lib/api/daemon.ts

Functions

f
start(): Task<boolean, never>

Ensures a daemon is running for this project, spawning one if there isn't.

f
status(): Task<DaemonStatus, never>

Probes this project's daemon. An actual round-trip over the socket, not a look at the sentinel file, so a crashed daemon that left its files behind reports { running: false }.

f
stop(): Task<boolean, never>

Stops this project's daemon. Resolves true if one was running, false if there was nothing to stop — either way, no daemon is running afterwards.

f
test(options?: DaemonRunOptions): Task<RunResult, Client.DaemonRunFailure>

Runs the suite inside this project's daemon and resolves with the same RunResult a local run would produce — reusing the daemon's browser and warm bundle instead of paying for a fresh one.

Interfaces

I
DaemonRunOptions

The options a daemon run accepts: everything from UserRunOptions that survives a socket.

Type Aliases

lib/api/index.ts

The qunitx JS API: run your QUnit tests in a real browser from Node.js or Deno, and get the results back as a value.

Classes

c
T
Stream<T, E = never>(open: () => AsyncGenerator<T | E>)

The exported value: builders (Stream.from, Stream.unfold, Stream.lines) are the only entry points — there is no public constructor and no call form.

Functions

f
Daemon.start(): Task<boolean, never>

Ensures a daemon is running for this project, spawning one if there isn't.

f
Daemon.status(): Task<DaemonStatus, never>

Probes this project's daemon. An actual round-trip over the socket, not a look at the sentinel file, so a crashed daemon that left its files behind reports { running: false }.

f
Daemon.stop(): Task<boolean, never>

Stops this project's daemon. Resolves true if one was running, false if there was nothing to stop — either way, no daemon is running afterwards.

f
Daemon.test(options?: DaemonRunOptions): Task<RunResult, Client.DaemonRunFailure>

Runs the suite inside this project's daemon and resolves with the same RunResult a local run would produce — reusing the daemon's browser and warm bundle instead of paying for a fresh one.

f
openSession(
input?: SessionOptions | string | string[],
extra?: SessionOptions
): Task<TestSession | WatchSession, RunFailure>
4 overloads

With watch: true, the DURABLE shape: one page behind a stable WatchSession.url, kept open with the rerun verbs and file watching, exactly as watch gives you.

f
run(
file: string,
options?: ScriptOptions
): Task<ScriptResult, ScriptFailure>

Runs ONE file as a plain script in a real browser — the API twin of qunitx run <file>, and the sibling of test, which runs a suite.

f
run(options?: InitOptions): Task<InitResult, ProjectRootNotFoundFailure>

Bootstraps a qunitx project: writes the test HTML template, updates package.json, and writes tsconfig.json when there isn't one.

f
run(options?: GenerateOptions): Task<GenerateResult, ProjectRootNotFoundFailure>

Writes a new test file from the boilerplate template, deriving the QUnit module name from the path. Never overwrites an existing file.

f
streamConsole(
stdout: { write(text: string): unknown; },
stderr?: { write(text: string): unknown; }
): Console

Adapts anything with a write(string) — a node:fs stream, a socket, an array-backed fake — into a Console. stderr defaults to stdout, so one stream collects the whole run.

f
test(
input?: UserRunOptions | string | string[],
extra?: UserRunOptions
): Task<RunResult, RunFailure>
2 overloads

Runs the suite once in a real browser and resolves with everything it produced.

f
validate(userRunOptions: UserRunOptions): void

Rejects options the run cannot honour, before anything is launched.

f
watch(
input?: UserRunOptions | string | string[],
extra?: UserRunOptions
): Task<WatchSession, RunFailure>
2 overloads

Starts a watch session: builds once, runs once, then re-runs on every save until closed.

Interfaces

I
Breakpoint

A breakpoint the session set, numbered the way gdb numbers them.

I
BrowserLog

A console.* call or an uncaught error from the page under test.

  • args: unknown[]

    The call's arguments, resolved to JSON values where the page could serialize them.

  • text: string

    The rendered single-line text. Always present.

  • type: string

    The page console type (log/warning/error/info/debug), or pageerror.

I
Console

Where a run's text goes: console, made injectable.

I
CoverageSummary

The run's line coverage: one entry per source file, plus the totals across all of them.

I
Daemon.DaemonRunOptions

The options a daemon run accepts: everything from UserRunOptions that survives a socket.

I
DaemonRunOptions

The options a daemon run accepts: everything from UserRunOptions that survives a socket.

I
FileCoverageSummary

Per-file line coverage, when coverage was requested.

I
FoundTest

One test found by the static scan, named exactly as QUnit would name it.

I
Frame

One frame of a stopped call stack.

  • index: number

    Its place on the stack: 0 is where the page stopped, and each one after it is the caller.

  • selected: boolean

    Whether the rest of the session is currently reading this one.

  • where: string

    Where it is, the same way a pause names itself.

I
GenerateOptions

Where to scaffold a test file. target is a project-relative path; a missing .js/.ts extension becomes .js, and missing directories are created.

  • cwd: string

    Directory to find the project root from. Defaults to process.cwd().

  • target: string

    Project-relative path of the test file to write.

I
GenerateResult

What generate did: the file it wrote, or the one it refused to overwrite.

I
InitOptions

Where to bootstrap, and which HTML fixtures to write. Both default the way the CLI does: the working directory, and whatever .html arguments were passed.

  • cwd: string

    Directory to find the project root from. Defaults to process.cwd().

  • htmlPaths: string[]

    HTML fixtures to create, relative to the project root. Defaults to ['test/tests.html'].

I
InitResult

What init did, so the caller can report it. Returned rather than printed: the CLI turns these into its messages, and a programmatic caller gets the same facts as data.

I
Notice

One diagnostic from qunitx itself: which files a narrowing flag scoped the run to, a filter that matched nothing, a build error, a timeout.

I
ReplResult

What one input produced. output is already rendered — the terminal prints it verbatim — and the rest is there so a caller can react rather than parse.

  • failed: boolean

    Whether the input worked. What output never says, and what a caller needs before it can do anything but print: colour the line red, decline to add it to the replayable history, exit non-zero on a piped session.

  • incomplete: boolean

    The input was unfinished (const a = {), so nothing ran and the terminal should read on.

  • output: string

    The text to print, whichever way it went: the rendered value when the input worked, the error and its source-mapped stack when it did not. Empty when there is nothing to print.

  • pausedAt: string

    Where the input stopped at a debugger statement, or absent when it ran to completion.

  • tests: TestDetails[]

    Tests QUnit ran because of this input. Already reported through the session's reporters.

  • thrown: boolean

    The failure was the page throwing, rather than the session refusing the input.

I
ReplSession

A live REPL: one browser page, kept open, that evaluates what you type.

I
Reporter

The reporter contract — the public extension point for observing a run. Reporters render it to text (the built-in tap/spec/dot/github), write an artifact (junit), or simply collect, which is how the JS API turns a run into a value.

I
ReporterContext

What a reporter is given on every hook: where to write, what the run has counted so far, and the few resolved paths a message needs.

I
ResolvedRun

What the run actually resolved to, after package.json, the defaults and the options were merged — the answers a caller cannot otherwise recover from what it passed in.

I
RunEndInfo

Final run info; the counts themselves live on config.state.results.counter.

I
RunGroup

One concurrent group of a run: the files bundled into a single page, and where that page's artifacts were written.

  • files: string[]

    Absolute paths of the test files this group bundled and ran together.

  • index: number

    Position in the run's group list, and the group-<index> suffix on output.

  • output: string

    Absolute path of this group's build output directory.

I
RunResult

Everything a finished run produced.

I
RunStartInfo

Run-scope counts. fileCount === null means "counts unknown at this point" (watch mode, where the header is emitted per browser connection rather than per file batch).

I
ScopeEntry

One thing in scope: what it is called, what it holds, and where it came from.

I
ScriptOptions

Everything run accepts beyond the file itself — the subset of a script run a caller can set.

I
ScriptResult

What one script run produced.

  • browserLogs: BrowserLog[]

    Everything the script printed — its console calls and any uncaught error — in emit order, whatever console option was passed. The same shape a test run reports, capped the same way.

  • browserLogsDropped: number

    How many lines were dropped to stay under the cap. 0 when nothing was.

  • durationMs: number

    Wall-clock ms from the first bundle to the script's top level settling.

  • exitCode: number

    globalThis.exitCode if the script set one, 1 if it threw, else 0.

  • file: string

    Absolute path of the file that ran.

  • ok: boolean

    True when the script finished with exit code 0.

  • tests: RunResult | null

    The suite the file declared, or null when it was a plain script.

  • value: unknown

    The script's export default, or undefined when it has none.

  • valueProblem: string | null

    Why ScriptResult.value is undefined even though the script exported something, or null when there is nothing to explain — including when it exported nothing at all.

I
SearchReport

What the static scan found, before anything is printed.

I
TestAssertion

One QUnit assertion inside a testEnd payload.

I
TestDetails

The QUnit testEnd payload as it arrives over the WebSocket. Passing tests carry the trimmed { status, fullName, runtime }; failing tests additionally carry assertions.

I
TestResult

One finished test.

I
TestSession

One run, watched as it happens.

  • abort(): Promise<void>

    Cuts the run short. The browser drops the rest of its queue and the run ends where it is, with aborted: true on the result — which TestSession.result still resolves with, so an aborted run is answered rather than left hanging.

  • close(): Promise<void>

    Closes the session, ending iteration. Idempotent; implied by await using.

  • droppedEvents: number

    How many events the feed dropped because a consumer fell behind. 0 in every ordinary run.

  • events(): Stream<RunEvent>

    The same events as iterating the session, as a Stream — so the combinators are there without a Stream.from wrapper.

  • result(): Promise<RunResult>

    The finished run. Starts it if nothing has yet, and resolves with the same RunResult the final runEnd event carries — never undefined, which is the whole reason this is a session rather than a bare event stream.

I
UnlistableCounts

Why some declarations could not be listed, split by cause.

I
UserRunOptions

Everything a run can be told to do. Every field is optional: run() with no arguments runs the project exactly as a bare qunitx would, minus the printing.

  • after: string | false

    Path to a module run after the tests; it receives the run's counters.

  • before: string | false

    Path to a module run before the tests; it receives the resolved config.

  • browser: "chromium" | "firefox" | "webkit"

    Browser engine. Defaults to chromium, the only one that can collect coverage.

  • changedSince: string

    Run only files whose transitive imports changed since this git ref ('HEAD' for uncommitted).

  • console: Console

    Where the run's text goes. Defaults to the process streams when a reporter is NAMED, and to silentConsole otherwise — so a programmatic run prints nothing unless it was asked to.

  • coverage: boolean | { formats?: Array<"lcov" | "html">; }

    Collect V8 line coverage (chromium only). formats additionally writes lcov/html artifacts.

  • cwd: string

    Directory the project root and relative inputs resolve against. Defaults to process.cwd().

  • debug: boolean

    Forward every page console call and print the server URL.

  • extensions: string[]

    File extensions treated as test files. Defaults to ['js', 'ts', 'jsx', 'tsx'].

  • failFast: boolean

    Stop the run at the first failing test.

  • filter: string

    Run only tests whose "Module: test name" matches. QUnit's own semantics: case-insensitive substring, /regex/, /regex/i, or a leading ! to invert.

  • html: string[]

    HTML fixture files to wrap the bundle in, relative to the project root.

  • inputs: string[]

    Files, directories, globs, or file.ts#34 line targets — the same grammar as the command line's positional arguments. Defaults to package.json#qunitx.inputs.

  • junit: boolean | string

    Write a JUnit XML report. true writes <output>/junit.xml; a string is a path.

  • onlyFailed: boolean

    Run only the files that failed last time, from the persistent failure cache.

  • open: boolean | string

    Open the output in a browser: true for the default, a string to name a binary.

  • output: string

    Directory for the compiled bundle and HTML output. Defaults to 'tmp'.

  • plugins: EsbuildPlugin[]

    esbuild plugins for the test bundle — live objects, not specifiers.

  • port: number

    Port for the local test server. Defaults to 1234, incrementing on conflict.

  • reporter: ReporterOption

    Print the run with ONE reporter: a built-in name ('tap', 'spec', 'dot', 'github'), your own Reporter, or false. The same spelling as the CLI's --reporter.

  • reporters: ReadonlyArray<ReporterOption>

    Print the run with SEVERAL. Mutually exclusive with UserRunOptions.reporter: pass reporter for one, reporters for many, and validate rejects both at once.

  • signal: AbortSignal

    Cancels the run when it fires.

  • timeout: number

    Milliseconds a single test may take before the run is declared stalled. Defaults to 20000.

I
WatchSession

A running watch session.

  • abort(): Promise<void>

    Cuts the current run short: the browser drops the rest of its queue and the run ends where it is, with aborted: true on its result.

  • browser: Browser

    Playwright's Browser for this session — unstable, playwright-core's type.

  • close(): Promise<void>

    Stops watching, closes the browser and server, and ends the iteration. Idempotent.

  • droppedEvents: number

    How many events the feed dropped because a consumer fell behind. 0 unless something stalls.

  • esbuild: BuildContext | null

    esbuild's incremental BuildContext — unstable, esbuild's type.

  • events(): Stream<RunEvent>

    The fine-grained feed: every event of every run, flat and in order, until the session closes.

  • fileWatchers: Record<string, FSWatcher>

    The live fs.watch handles keyed by watched path — unstable, and a PARTIAL view: the parent-directory watchers, rescan intervals and symlink pollers the session also owns are not in here. It answers "what is being watched", not "every handle the watcher holds".

  • initial: RunResult

    The initial run's result, available before anything has changed on disk.

  • latest: RunResult

    The most recent run's result — initial until something re-runs.

  • page: Page

    The page the suite runs in — unstable, playwright-core's type.

  • restart(patch?: SessionPatch): Promise<RunResult>

    Tears the session's machinery down and boots it again — browser, page, server, esbuild context and watchers — then runs the suite once, resolving with that run's result.

  • results(): Stream<RunResult>

    The coarse feed as a Stream: one complete RunResult per rerun, with the combinators attached. for await (const r of session) is the same sequence.

  • run(files?: string[]): Promise<RunResult>

    Re-runs now, optionally scoped to files, and resolves with that run's result.

  • runAll(): Promise<RunResult>

    Runs the whole suite, dropping any line-target selectors this session was scoped to.

  • runFailed(): Promise<RunResult>

    Re-runs the files that last failed, or repeats the last run when nothing has failed yet — saying so as an info notice on the result rather than silently doing something else.

  • running: boolean

    Whether a run is in flight right now.

  • url: string

    Where the QUnit view is being served, e.g. http://localhost:1234.

  • webServer: HTTPServer

    The HTTP + WebSocket server serving the bundle — unstable, this project's own HTTPServer rather than a documented interface.

Namespaces

N
Daemon

Daemon control: start, stop, status, and a test that reuses the daemon's warm browser and returns the same RunResult a local run does.

Type Aliases

T
AnyFailure = Any

A declared failure: something the runner decided it could not do, carrying a code to branch on and a message to show.

T
Daemon.DaemonRunFailure = Failure.Of<DaemonUnreachable | DaemonDisconnected | DaemonSilent>

Every way a daemon-routed run can fail to produce an exit code.

T
DaemonRunFailure = Failure.Of<DaemonUnreachable | DaemonDisconnected | DaemonSilent>

Every way a daemon-routed run can fail to produce an exit code.

T
InvalidOptionFailure = Failure.Of<InvalidOption>

The one failure validate raises.

T
ReplFailure = RunFailure | ReplStartFailure

Every way a REPL can fail to open: the same config failures a run has, plus the two of its own — a browser that cannot be driven over CDP, and a preload file that will not compile.

T
ReplStartFailure =
Failure.Of<UnsupportedBrowser>
| Failure.Of<PreloadBuildFailed>

Everything starting a REPL can fail with, beyond config assembly.

T
ReporterName = (REPORTERS)[number]

A valid --reporter value.

T
ReporterOption = ReporterName | Reporter | false

--reporter by name, a reporter of your own, or false for none.

T
Result<T, E = Any> = T | E

The success value or a declared failure — a bare union, discriminated by the Failure brand.

T
RunCounts = Counter

Outcome totals for a run. total is the sum of the four buckets; assertionsFailed counts individual assertions rather than tests, so one test can contribute several.

T
RunFailure = ConfigFailure | InvalidOptionFailure

Every way a run can fail to happen: an option the runner will not accept, an unreadable input, a directory with no package.json above it, an esbuild plugin that will not load.

T
ScriptFailure =
Failure.Of<NotAScriptFile | RunCommand.ScriptBuildFailed>
| ScriptEntryFailure

Every way run can reject. A script that merely exits non-zero is NOT one of them.

T
SessionOptions = UserRunOptions & { watch?: boolean; }

What openSession accepts: every run option, plus the one that chooses the shape.

T
StepKind = "into" | "over" | "out"

One step, named the way gdb names it: step, next, finish.

Variables

v
processConsole: Console

The CLI's: the real process streams. .write is looked up per call, so the daemon's stdout interception still reaches it.

v
REPORTERS: "tap" | "spec" | "dot" | "github"[]

Every stdout reporter --reporter accepts, in help/error-message order. Exactly one is active per run — artifact outputs (--junit, --coverage) are separate additive flags. This module is a leaf (type-only imports), so Args.parse can validate against it without pulling the reporter implementations into the CLI's startup path.

v
silentConsole: Console

Discards everything. The JS API's default, so a programmatic run prints nothing unless it was asked to.

v
T
Task: TaskConstructor

Call-or-construct, like Boolean/Date: Task(recipe) and new Task(recipe) build the same lazy Task. ES classes reject the call form, so the export is a Proxy whose apply forwards to construction — statics, instanceof, and the prototype all pass through.

lib/api/options.ts

Functions

f
from(
input?: UserRunOptions | string | string[],
options?: UserRunOptions
): ConfigOptions & { reporters: Reporter[]; }

Public options in, runner input out — this is the API's Args.parse, and the only reason UserRunOptions and ConfigOptions are separate types.

f
validate(userRunOptions: UserRunOptions): void

Rejects options the run cannot honour, before anything is launched.

Interfaces

I
UserRunOptions

Everything a run can be told to do. Every field is optional: run() with no arguments runs the project exactly as a bare qunitx would, minus the printing.

  • after: string | false

    Path to a module run after the tests; it receives the run's counters.

  • before: string | false

    Path to a module run before the tests; it receives the resolved config.

  • browser: "chromium" | "firefox" | "webkit"

    Browser engine. Defaults to chromium, the only one that can collect coverage.

  • changedSince: string

    Run only files whose transitive imports changed since this git ref ('HEAD' for uncommitted).

  • console: Console

    Where the run's text goes. Defaults to the process streams when a reporter is NAMED, and to silentConsole otherwise — so a programmatic run prints nothing unless it was asked to.

  • coverage: boolean | { formats?: Array<"lcov" | "html">; }

    Collect V8 line coverage (chromium only). formats additionally writes lcov/html artifacts.

  • cwd: string

    Directory the project root and relative inputs resolve against. Defaults to process.cwd().

  • debug: boolean

    Forward every page console call and print the server URL.

  • extensions: string[]

    File extensions treated as test files. Defaults to ['js', 'ts', 'jsx', 'tsx'].

  • failFast: boolean

    Stop the run at the first failing test.

  • filter: string

    Run only tests whose "Module: test name" matches. QUnit's own semantics: case-insensitive substring, /regex/, /regex/i, or a leading ! to invert.

  • html: string[]

    HTML fixture files to wrap the bundle in, relative to the project root.

  • inputs: string[]

    Files, directories, globs, or file.ts#34 line targets — the same grammar as the command line's positional arguments. Defaults to package.json#qunitx.inputs.

  • junit: boolean | string

    Write a JUnit XML report. true writes <output>/junit.xml; a string is a path.

  • onlyFailed: boolean

    Run only the files that failed last time, from the persistent failure cache.

  • open: boolean | string

    Open the output in a browser: true for the default, a string to name a binary.

  • output: string

    Directory for the compiled bundle and HTML output. Defaults to 'tmp'.

  • plugins: EsbuildPlugin[]

    esbuild plugins for the test bundle — live objects, not specifiers.

  • port: number

    Port for the local test server. Defaults to 1234, incrementing on conflict.

  • reporter: ReporterOption

    Print the run with ONE reporter: a built-in name ('tap', 'spec', 'dot', 'github'), your own Reporter, or false. The same spelling as the CLI's --reporter.

  • reporters: ReadonlyArray<ReporterOption>

    Print the run with SEVERAL. Mutually exclusive with UserRunOptions.reporter: pass reporter for one, reporters for many, and validate rejects both at once.

  • signal: AbortSignal

    Cancels the run when it fires.

  • timeout: number

    Milliseconds a single test may take before the run is declared stalled. Defaults to 20000.

Type Aliases

T
InvalidOptionFailure = Failure.Of<InvalidOption>

The one failure validate raises.

T
ReporterOption = ReporterName | Reporter | false

--reporter by name, a reporter of your own, or false for none.

Variables

lib/api/repl.ts

Functions

Type Aliases

T
ReplFailure = RunFailure | ReplStartFailure

Every way a REPL can fail to open: the same config failures a run has, plus the two of its own — a browser that cannot be driven over CDP, and a preload file that will not compile.

lib/api/reporter.ts

Classes

c
APIReporter

The reporter that turns a run into a value: it records what happened instead of printing it.

Functions

f
findAPIReporterFrom(config: Config): APIReporter

The APIReporter watching this run — the reporter every result is built from.

f
toTestResult(details: TestDetails): TestResult

Projects one QUnit testEnd payload into a TestResult. QUnit's fullName is [...modules, testName]; the joined display form matches what filter matches against — modules separated by >, the test name after a : .

Type Aliases

Variables

v
CHANNEL_CAPACITY: 10000

The buffer a feed keeps for a consumer that has stopped reading.

v
MAX_BROWSER_LOGS: 1000

How many page-log entries a result retains. Tests and notices are bounded by the suite; page output is bounded by nothing a test runner controls, so this is the one channel that needs a ceiling. Generous enough that a real run never reaches it.

lib/api/run.ts

Functions

f
run(
file: string,
options?: ScriptOptions
): Task<ScriptResult, ScriptFailure>

Runs ONE file as a plain script in a real browser — the API twin of qunitx run <file>, and the sibling of test, which runs a suite.

Interfaces

I
ScriptOptions

Everything run accepts beyond the file itself — the subset of a script run a caller can set.

I
ScriptResult

What one script run produced.

  • browserLogs: BrowserLog[]

    Everything the script printed — its console calls and any uncaught error — in emit order, whatever console option was passed. The same shape a test run reports, capped the same way.

  • browserLogsDropped: number

    How many lines were dropped to stay under the cap. 0 when nothing was.

  • durationMs: number

    Wall-clock ms from the first bundle to the script's top level settling.

  • exitCode: number

    globalThis.exitCode if the script set one, 1 if it threw, else 0.

  • file: string

    Absolute path of the file that ran.

  • ok: boolean

    True when the script finished with exit code 0.

  • tests: RunResult | null

    The suite the file declared, or null when it was a plain script.

  • value: unknown

    The script's export default, or undefined when it has none.

  • valueProblem: string | null

    Why ScriptResult.value is undefined even though the script exported something, or null when there is nothing to explain — including when it exported nothing at all.

Type Aliases

T
ScriptFailure =
Failure.Of<NotAScriptFile | RunCommand.ScriptBuildFailed>
| ScriptEntryFailure

Every way run can reject. A script that merely exits non-zero is NOT one of them.

Variables

lib/api/search.ts

Functions

f
search(options?: UserRunOptions | string | string[]): Task<SearchResult, RunFailure>

Lists the tests a selection would run, without running them.

Interfaces

I
FoundTest

One test found by the static scan, named exactly as QUnit would name it.

I
SearchReport

What the static scan found, before anything is printed.

I
UnlistableCounts

Why some declarations could not be listed, split by cause.

lib/api/session.ts

Functions

f
openSession(
input?: SessionOptions | string | string[],
extra?: SessionOptions
): Task<TestSession | WatchSession, RunFailure>
4 overloads

With watch: true, the DURABLE shape: one page behind a stable WatchSession.url, kept open with the rerun verbs and file watching, exactly as watch gives you.

Interfaces

I
TestSession

One run, watched as it happens.

  • abort(): Promise<void>

    Cuts the run short. The browser drops the rest of its queue and the run ends where it is, with aborted: true on the result — which TestSession.result still resolves with, so an aborted run is answered rather than left hanging.

  • close(): Promise<void>

    Closes the session, ending iteration. Idempotent; implied by await using.

  • droppedEvents: number

    How many events the feed dropped because a consumer fell behind. 0 in every ordinary run.

  • events(): Stream<RunEvent>

    The same events as iterating the session, as a Stream — so the combinators are there without a Stream.from wrapper.

  • result(): Promise<RunResult>

    The finished run. Starts it if nothing has yet, and resolves with the same RunResult the final runEnd event carries — never undefined, which is the whole reason this is a session rather than a bare event stream.

Type Aliases

T
SessionOptions = UserRunOptions & { watch?: boolean; }

What openSession accepts: every run option, plus the one that chooses the shape.

lib/api/test.ts

Functions

f
abort(config: ResolvedConfig): RunResult

The result of a run that was cancelled before it began: no browser, no tests, aborted: true.

f
buildResult(
config: ResolvedConfig,
outcome: RunOutcome
): RunResult

Assembles the run's result from the config's accumulated state, the run's outcome, and the APIReporter that watched it.

f
test(
input?: UserRunOptions | string | string[],
extra?: UserRunOptions
): Task<RunResult, RunFailure>
2 overloads

Runs the suite once in a real browser and resolves with everything it produced.

Interfaces

I
CoverageSummary

The run's line coverage: one entry per source file, plus the totals across all of them.

I
FileCoverageSummary

Per-file line coverage, when coverage was requested.

I
ResolvedRun

What the run actually resolved to, after package.json, the defaults and the options were merged — the answers a caller cannot otherwise recover from what it passed in.

I
RunGroup

One concurrent group of a run: the files bundled into a single page, and where that page's artifacts were written.

  • files: string[]

    Absolute paths of the test files this group bundled and ran together.

  • index: number

    Position in the run's group list, and the group-<index> suffix on output.

  • output: string

    Absolute path of this group's build output directory.

I
RunResult

Everything a finished run produced.

I
TestResult

One finished test.

Type Aliases

T
RunCounts = Counter

Outcome totals for a run. total is the sum of the four buckets; assertionsFailed counts individual assertions rather than tests, so one test can contribute several.

T
RunFailure = ConfigFailure | InvalidOptionFailure

Every way a run can fail to happen: an option the runner will not accept, an unreadable input, a directory with no package.json above it, an esbuild plugin that will not load.

lib/api/watch.ts

Functions

f
watch(
input?: UserRunOptions | string | string[],
extra?: UserRunOptions
): Task<WatchSession, RunFailure>
2 overloads

Starts a watch session: builds once, runs once, then re-runs on every save until closed.

Interfaces

I
WatchSession

A running watch session.

  • abort(): Promise<void>

    Cuts the current run short: the browser drops the rest of its queue and the run ends where it is, with aborted: true on its result.

  • browser: Browser

    Playwright's Browser for this session — unstable, playwright-core's type.

  • close(): Promise<void>

    Stops watching, closes the browser and server, and ends the iteration. Idempotent.

  • droppedEvents: number

    How many events the feed dropped because a consumer fell behind. 0 unless something stalls.

  • esbuild: BuildContext | null

    esbuild's incremental BuildContext — unstable, esbuild's type.

  • events(): Stream<RunEvent>

    The fine-grained feed: every event of every run, flat and in order, until the session closes.

  • fileWatchers: Record<string, FSWatcher>

    The live fs.watch handles keyed by watched path — unstable, and a PARTIAL view: the parent-directory watchers, rescan intervals and symlink pollers the session also owns are not in here. It answers "what is being watched", not "every handle the watcher holds".

  • initial: RunResult

    The initial run's result, available before anything has changed on disk.

  • latest: RunResult

    The most recent run's result — initial until something re-runs.

  • page: Page

    The page the suite runs in — unstable, playwright-core's type.

  • restart(patch?: SessionPatch): Promise<RunResult>

    Tears the session's machinery down and boots it again — browser, page, server, esbuild context and watchers — then runs the suite once, resolving with that run's result.

  • results(): Stream<RunResult>

    The coarse feed as a Stream: one complete RunResult per rerun, with the combinators attached. for await (const r of session) is the same sequence.

  • run(files?: string[]): Promise<RunResult>

    Re-runs now, optionally scoped to files, and resolves with that run's result.

  • runAll(): Promise<RunResult>

    Runs the whole suite, dropping any line-target selectors this session was scoped to.

  • runFailed(): Promise<RunResult>

    Re-runs the files that last failed, or repeats the last run when nothing has failed yet — saying so as an info notice on the result rather than silently doing something else.

  • running: boolean

    Whether a run is in flight right now.

  • url: string

    Where the QUnit view is being served, e.g. http://localhost:1234.

  • webServer: HTTPServer

    The HTTP + WebSocket server serving the bundle — unstable, this project's own HTTPServer rather than a documented interface.

Type Aliases

lib/args/index.ts

Functions

f
applyInputs(
flags: ParsedFlags,
projectRoot: string,
cwd: string,
positional: readonly string[]
): ParsedFlags

Classifies raw positional targets into flags, exactly as the CLI does with argv positionals: .html fixtures become htmlPaths, a trailing #34/:34 becomes a lineTargets entry, and everything else is an absolute path in inputs (deduplicated) plus an inputsWithoutLineTargets mention.

f
parse(
projectRoot: string,
argv?: readonly string[],
cwd?: string
): Result<ParsedFlags, ParseFailure>

Parses an argv into a qunitx flag object (inputs, debug, watch, failFast, timeout, output, port, before, after).

f
tokenize(args: readonly string[]): ArgToken[]

Classifies argv into query / flag / input tokens.

Interfaces

I
FlagToken

Any other flag (--watch, --timeout=5000, -o, …), passed through verbatim.

I
InputToken

A positional target: a file, folder, or glob (possibly with a #34 line suffix).

I
ParsedFlags

The flag object a successful parse() carries — CLI flags only, before package.json merge.

I
QueryToken

A query flag (-t/-m/-s/-p) with its resolved value.

Type Aliases

T
ArgToken = QueryToken | FlagToken | InputToken

One classified argv entry: a query flag, any other flag, or a positional input.

T
ParseFailure = Failure.Of<InvalidFlag>

Every way parse() can reject its input.

Variables

lib/args/parse.ts

Functions

f
applyInputs(
flags: ParsedFlags,
projectRoot: string,
cwd: string,
positional: readonly string[]
): ParsedFlags

Classifies raw positional targets into flags, exactly as the CLI does with argv positionals: .html fixtures become htmlPaths, a trailing #34/:34 becomes a lineTargets entry, and everything else is an absolute path in inputs (deduplicated) plus an inputsWithoutLineTargets mention.

f
parse(
projectRoot: string,
argv?: readonly string[],
cwd?: string
): Result<ParsedFlags, ParseFailure>

Parses an argv into a qunitx flag object (inputs, debug, watch, failFast, timeout, output, port, before, after).

Interfaces

I
ParsedFlags

The flag object a successful parse() carries — CLI flags only, before package.json merge.

Type Aliases

T
ParseFailure = Failure.Of<InvalidFlag>

Every way parse() can reject its input.

Variables

lib/args/tokenize.ts

Functions

f
tokenize(args: readonly string[]): ArgToken[]

Classifies argv into query / flag / input tokens.

Interfaces

I
FlagToken

Any other flag (--watch, --timeout=5000, -o, …), passed through verbatim.

I
InputToken

A positional target: a file, folder, or glob (possibly with a #34 line suffix).

I
QueryToken

A query flag (-t/-m/-s/-p) with its resolved value.

Type Aliases

T
ArgToken = QueryToken | FlagToken | InputToken

One classified argv entry: a query flag, any other flag, or a positional input.

lib/chrome/chromium-args.ts

Variables

v
CHROMIUM_ARGS: string[]

Launch args passed to Chromium for both the CDP pre-launch spawn and the playwright fallback launch.

lib/chrome/cleanup-dir.ts

Functions

f
cleanupDir(dirPath: string): Promise<void>

Kills all surviving processes that reference dirPath (checked via cwd, cmdline, and open file descriptors), then retries fs.rm() until it succeeds or a 5-second deadline expires. Re-scans /proc on each failed rm() attempt to catch processes that appear after the initial scan (e.g. late-forked Chrome helpers).

lib/chrome/find.ts

Functions

f
find(): Promise<string | null>

Resolves the Chrome/Chromium executable path. Returns a Promise for API compatibility with callers, but the resolution is synchronous.

lib/chrome/index.ts

Functions

f
cleanupDir(dirPath: string): Promise<void>

Kills all surviving processes that reference dirPath (checked via cwd, cmdline, and open file descriptors), then retries fs.rm() until it succeeds or a 5-second deadline expires. Re-scans /proc on each failed rm() attempt to catch processes that appear after the initial scan (e.g. late-forked Chrome helpers).

f
find(): Promise<string | null>

Resolves the Chrome/Chromium executable path. Returns a Promise for API compatibility with callers, but the resolution is synchronous.

f
spawn(
chromePath: string | null | undefined,
args: string[],
headless?: boolean,
onSpawn?: (handle: ChromeHandle) => void
): Promise<EarlyChrome | null>

Spawns a headless Chrome process with --remote-debugging-port=0 and resolves once the CDP WebSocket endpoint appears on stderr. Returns null if Chrome is unavailable or fails to start, so callers can fall back to playwright's normal chromium.launch().

Variables

v
CHROMIUM_ARGS: string[]

Launch args passed to Chromium for both the CDP pre-launch spawn and the playwright fallback launch.

lib/chrome/prelaunch.ts

Functions

f
prelaunchPromise(): Promise<EarlyChrome | null>

The in-flight pre-launch, or a resolved null when startPrelaunch was never called or decided not to spawn (non-run command, --search, a daemon-routed run, non-chromium, or macOS — where the CI runner installs playwright's own headless shell and its path is not known this early).

f
shutdownPrelaunch(): Promise<void>

Kills the pre-launched Chrome process and awaits its async temp-dir cleanup. Must be called before process.exit() so the event loop is still alive and the async rm() inside spawn's close handler can run to completion. Safe to call multiple times or when Chrome was never pre-launched (no-op).

f
startPrelaunch(): void

Starts Chrome now, if this invocation is one that will need it.

lib/chrome/spawn.ts

Functions

f
spawn(
chromePath: string | null | undefined,
args: string[],
headless?: boolean,
onSpawn?: (handle: ChromeHandle) => void
): Promise<EarlyChrome | null>

Spawns a headless Chrome process with --remote-debugging-port=0 and resolves once the CDP WebSocket endpoint appears on stderr. Returns null if Chrome is unavailable or fails to start, so callers can fall back to playwright's normal chromium.launch().

lib/commands/daemon/client.ts

Functions

f
ping(): Promise<ResponseChunk | null>

Sends a ping and resolves the daemon's pong response (or null on failure).

f
run(
options: DaemonRunOptions,
sink?: Console
): Task<RunResult, DaemonRunFailure>

Runs the suite inside the daemon and answers with the same RunResult a local run produces. The one to reach for; runArgv is the CLI's narrower variant.

f
runArgv(argv: string[]): Task<number, DaemonRunFailure>

The CLI's path: sends raw argv and answers with the exit code the daemon reported.

f
shouldAutoSpawn(): boolean

True iff the user opted in to auto-spawn (QUNITX_DAEMON=1), the invocation is daemon-eligible, and no daemon is running yet — meaning cli should spawn one before dispatching the run.

f
shouldUse(): boolean

True iff a live daemon socket exists and the invocation can use it. The cli's primary dispatch check.

f
shutdown(cwd?: string): Promise<boolean>

Sends shutdown and waits until the daemon has actually fully exited — not just until the socket closes. The daemon's dispatch handler acks 'done' before its async cleanup (server.close / browser.close / process.exit) runs, so a naive "stop returned" signal leaves the daemon's socket / named-pipe handle still held. A fast follow-up daemon start would then race the dying daemon and hit EADDRINUSE — observed reliably on Windows where named-pipe handle release lags process exit by tens of milliseconds.

f
tryConnect(cwd?: string): Promise<net.Socket | null>

Opens a connection to the daemon for the given cwd. Resolves the connected socket on success; resolves null on any failure (no socket file, ECONNREFUSED, timeout).

Type Aliases

T
DaemonRunFailure = Failure.Of<DaemonUnreachable | DaemonDisconnected | DaemonSilent>

Every way a daemon-routed run can fail to produce an exit code.

Variables

v
DaemonDisconnected: Failure.FailureFactory<
"DaemonDisconnected",
{ reason: "close" | "error" | "no-result"; }
>

The daemon accepted the run and then dropped the connection without a terminal message.

v
DaemonSilent: Failure.FailureFactory<"DaemonSilent", { ms: number; }>

The daemon accepted the connection and then said nothing for RUN_SILENCE_TIMEOUT_MS.

v
RUN_SILENCE_TIMEOUT_MS: 240000

How long the client waits on total silence before declaring the daemon wedged.

lib/commands/daemon/hint.ts

Functions

f
maybePrint(
ctx: HintContext,
opts?: PrintOpts
): Promise<void>

Prints the daemon-mode tip to stderr and creates a sentinel file so the tip is shown at most once per machine — users who already know about the daemon shouldn't be nagged. All filesystem I/O is best-effort: a sentinel-write failure just means the hint shows again on the next eligible run.

f
shouldShow(ctx: HintContext): boolean

Pure check: returns true iff the run context permits the hint. Covers env-var opt-outs, watch / daemon modes (own browser lifecycle), CI (auto-bypassed), the fast-run threshold, and TTY presence. No filesystem access.

Interfaces

I
HintContext

Run context consumed by the daemon-hint eligibility check.

I
PrintOpts

Side-effect injection points for maybePrint — testing seams.

lib/commands/daemon/index.ts

Functions

f
ensureRunning(): Promise<boolean>

Ensures a daemon is reachable for the current cwd. Returns true if one was already running or was successfully spawned; false on spawn timeout. Silent — intended for the cli.ts auto-spawn path where the spawn is incidental to the run.

f
run(): Promise<number>

Dispatches qunitx daemon <subcommand>. _serve runs the in-process daemon loop (spawned by start); all other subcommands are client operations. No subcommand (or --help / -h / help) prints usage and exits 0; an unknown subcommand prints usage to stderr and exits 1.

lib/commands/daemon/parse-idle-timeout.ts

Functions

f
parseIdleTimeout(value: string | undefined): ParsedIdleTimeout

Parses the QUNITX_DAEMON_IDLE_TIMEOUT env value. Accepts:

Interfaces

I
ParsedIdleTimeout

Result of parseIdleTimeout: the resolved idle window plus an optional human-readable warning the caller should surface to the user.

  • ms: number

    Milliseconds. Infinity when the user opts out of auto-shutdown via "false".

  • warning: string | null

    Human-readable explanation, set when the env value was malformed. The caller is expected to print this on the spawning CLI's stderr so the user sees it — the daemon process detaches with stdio: 'ignore', so a warning emitted from inside the daemon would be invisible.

Variables

v
DEFAULT_IDLE_TIMEOUT_MS: number

Default daemon idle window: 30 minutes after the last run finishes. Long enough for typical edit/run/edit bursts, short enough that a forgotten daemon reclaims resources without manual intervention.

lib/commands/daemon/paths.ts

Functions

f
dir(cwd?: string): string

Returns the per-cwd subdirectory that holds the daemon's info file, lockfile, and any future per-daemon state.

f
info(cwd?: string): string

Returns the per-cwd sidecar JSON path that mirrors the daemon's socket. Lets daemon status introspect daemon identity (pid, node version, uptime) without an IPC roundtrip, and serves as the cross-platform "is a daemon present?" sentinel since Windows named pipes are not visible on the regular filesystem.

f
socket(
cwd?: string,
platform?: NodeJS.Platform
): string

Returns the per-cwd path the daemon listens on. Platform-specific:

lib/commands/daemon/protocol.ts

Interfaces

I
DaemonInfo

Sidecar JSON file written next to the socket; lets daemon status show details without an IPC roundtrip.

I
DaemonRunOptions

The options a daemon run accepts: everything from UserRunOptions that survives a socket.

I
PingRequest

Client → daemon: liveness/identity probe.

I
RunRequest

Client → daemon: request to execute a test run with the given argv/env in the daemon's persistent Chrome.

  • argv: string[]

    CLI arguments after qunitx (i.e. process.argv.slice(2)).

  • cwd: string

    Client's working directory. The daemon rejects mismatched cwds (different project).

  • env: Record<string, string | undefined>

    Client's full process environment, applied to the run and restored on completion.

  • nodeVersion: string

    Client's process.version. The daemon shuts down on mismatch to avoid module-graph drift.

  • options: DaemonRunOptions

    Resolved options, when the request came from the JS API rather than a command line. Present means "build the config from these and answer with a result chunk"; absent means the daemon parses argv exactly as the CLI would.

  • type: "run"

    Discriminator for the protocol union; always 'run' for this variant.

I
ShutdownRequest

Client → daemon: graceful shutdown request.

Type Aliases

lib/commands/daemon/server.ts

Functions

f
browserResponsive(
browser:
Pick<PlaywrightBrowser, "isConnected">
& { newBrowserCDPSession?: PlaywrightBrowser["newBrowserCDPSession"]; },
browserName: string,
timeoutMs?: number
): Promise<boolean>

Actively confirms the browser's CDP channel is alive, unlike the passive isConnected() flag which lags a killed browser under load. Does a real newBrowserCDPSession round-trip bounded by timeoutMs — a dead or wedged channel resolves false within the budget rather than hanging. Chromium-only: firefox/webkit use a pipe transport whose 'disconnected' fires promptly on process exit, so isConnected() is already reliable there.

f
noteBrowserOutcome(
state: Pick<DaemonState, "consecutiveCrashes">,
connected: boolean
): "ok" | "relaunch" | "shutdown"

The crash-budget policy, as one pure decision: what a run's outcome does to the counter, and what should happen next.

f
removeLivenessFiles(state: DaemonState): Promise<void>

Unlinks the daemon's on-disk liveness markers: the socket, the info file, and the lock. socket/info are gated on listenSucceeded — a daemon that reaches shutdown without ever binding (e.g. an early throw) doesn't own them, and unlinking would corrupt whatever started in its place. The lock IS ours unconditionally: reaching shutdown means tryAcquireDaemonLock returned true, so always release it or the next spawn has to stale-pid-recover.

f
serve(): Promise<void>

Daemon process entry point. Owns one persistent Chrome and one Unix socket; serves run requests serially. Shuts down on SIGTERM/SIGINT, idle timeout, package.json mutation, node version mismatch, or an explicit shutdown request.

f
shutdown(
state: DaemonState,
reason: string,
exit?: () => void
): Promise<void>

Tears the daemon down: notifies pending clients, removes the on-disk liveness markers, closes the browser/esbuild within a bounded grace, then exits. Idempotent via state.shuttingDown. exit is injectable only so the shutdown ordering can be tested without killing the test process; production always uses the default. See test/commands/daemon-shutdown-test.ts.

Variables

v
BROWSER_PROBE_TIMEOUT_MS: 3000

Budget for the pre-run liveness probe. A healthy CDP round-trip answers in single-digit ms even on a loaded runner, so this is generous headroom: if it elapses, the browser is dead (or wedged) and recovery relaunches a fresh one. Kept well under GROUP_TIMEOUT_MS so a doomed browser surfaces in seconds, not the 3-minute last-resort deadline.

v
BROWSER_RELAUNCH_TIMEOUT_MS: 90000

How long a crash relaunch may take before the daemon declares itself unrecoverable.

lib/commands/daemon/socket.ts

Functions

f
connect(
socketPath: string,
timeoutMs: number
): Promise<net.Socket | null>

Attempts a connection to the given path (POSIX socket or Windows named pipe). Resolves the connected socket on success, null on any failure (peer absent, ECONNREFUSED, ENOENT, timeout). Lets net.createConnection produce the error directly — a pre-emptive existsSync check would not work for Windows named pipes (they live in \\.\pipe\..., not on the regular filesystem).

f
readMessages<T>(
socket: net.Socket,
onLine: (line: T) => void
): void

Reads NDJSON from socket, dispatching each parsed object via onLine. Tolerates packet splits across line boundaries; silently drops malformed lines. Used by both the daemon server (parsing client requests) and the client (parsing server responses).

lib/commands/generate.ts

Functions

f
run(options?: GenerateOptions): Task<GenerateResult, ProjectRootNotFoundFailure>

Writes a new test file from the boilerplate template, deriving the QUnit module name from the path. Never overwrites an existing file.

Interfaces

I
GenerateOptions

Where to scaffold a test file. target is a project-relative path; a missing .js/.ts extension becomes .js, and missing directories are created.

  • cwd: string

    Directory to find the project root from. Defaults to process.cwd().

  • target: string

    Project-relative path of the test file to write.

I
GenerateResult

What generate did: the file it wrote, or the one it refused to overwrite.

lib/commands/help.ts

Functions

f
run(): void

Prints qunitx-cli usage information to stdout.

lib/commands/init.ts

Functions

f
run(options?: InitOptions): Task<InitResult, ProjectRootNotFoundFailure>

Bootstraps a qunitx project: writes the test HTML template, updates package.json, and writes tsconfig.json when there isn't one.

Interfaces

I
InitOptions

Where to bootstrap, and which HTML fixtures to write. Both default the way the CLI does: the working directory, and whatever .html arguments were passed.

  • cwd: string

    Directory to find the project root from. Defaults to process.cwd().

  • htmlPaths: string[]

    HTML fixtures to create, relative to the project root. Defaults to ['test/tests.html'].

I
InitResult

What init did, so the caller can report it. Returned rather than printed: the CLI turns these into its messages, and a programmatic caller gets the same facts as data.

lib/commands/repl/command.ts

Functions

f
define(
repl: ReplContext,
commands: Record<string, ReplCommand>
): void

Registers commands on the server under their own names and every alias.

f
failureText(result: Repl.ReplResult): string

A failed result, in the words its kind earns.

Interfaces

I
ReplCommand

One command the prompt answers to: the sentence .help prints beside it, and what typing it does.

I
ReplContext

Everything a command may need that is not its argument.

lib/commands/repl/commands/back.ts

Variables

v
command: ReplCommand

.back — the same direction as .up, under the name that says why.

lib/commands/repl/commands/backtrace.ts

Variables

v
command: ReplCommand

.backtrace — the call stack, newest first, numbered from where it stopped.

lib/commands/repl/commands/break.ts

Variables

v
command: ReplCommand

.break — one name, two jobs, told apart by whether anything follows it.

lib/commands/repl/commands/breakpoints.ts

Variables

v
command: ReplCommand

.breakpoints — what this session has asked the page to stop on, and their numbers.

lib/commands/repl/commands/cat.ts

Functions

f
withLineNumbers(
contents: string,
file: string,
palette: Theme
): string

A file with its lines numbered, the way anybody quoting one writes them down.

Variables

v
command: ReplCommand

.cat — a file printed, numbered and highlighted.

lib/commands/repl/commands/clear.ts

Variables

v
command: ReplCommand

.clear — the screen, and nothing else.

lib/commands/repl/commands/continue.ts

Variables

v
command: ReplCommand

.continue — let a stopped page carry on.

lib/commands/repl/commands/copy.ts

Functions

f
clipboardCommands(platform: string): Array<[string, string[]]>

The command this platform copies with, and the arguments it takes.

Variables

v
command: ReplCommand

.copy — a value onto the clipboard.

lib/commands/repl/commands/delete.ts

Variables

v
command: ReplCommand

.delete — one name, two jobs, told apart by what follows it, exactly as .break is.

lib/commands/repl/commands/devtools.ts

Variables

v
command: ReplCommand

.devtools — Chrome's own DevTools, on the very page the prompt is driving.

lib/commands/repl/commands/doc.ts

Variables

v
command: ReplCommand

.doc — where a value is written, what was said above it, and how to call it.

lib/commands/repl/commands/down.ts

Variables

v
command: ReplCommand

.down — back toward where the page actually stopped.

lib/commands/repl/commands/finish.ts

Variables

v
command: ReplCommand

.finish — run until this frame returns, and stop in whoever called it. gdb's finish.

lib/commands/repl/commands/frame.ts

Variables

v
command: ReplCommand

.frame — which frame is being read, or an absolute one to go to.

lib/commands/repl/commands/header.ts

Variables

v
command: ReplCommand

.header — what this session will send, and what the last request actually sent and got back.

v
plural: ReplCommand

.headers — the same command, answering the plural's question when nothing follows it.

lib/commands/repl/commands/help.ts

Functions

f
commandListing(
commands: Readonly<Record<string, RegisteredCommand | undefined>>,
palette: Theme
): string

The listing .help prints: every command on a line of its own, aliases folded onto the line they are an alias OF, names in a column.

Variables

v
command: ReplCommand

.help — every command with nothing after it, and the documentation for whatever follows it.

lib/commands/repl/commands/here.ts

Variables

v
command: ReplCommand

.here — which frame is being read, and nothing else: it takes no count.

lib/commands/repl/commands/history.ts

Functions

f
lastEntered(
newestFirst: readonly string[],
count: number,
palette: Theme
): string

The last count lines entered, numbered, the way history prints them.

Variables

v
command: ReplCommand

.history — the last lines entered, numbered, the way a shell's does.

lib/commands/repl/commands/http.ts

Functions

f
buildCommand(verb: string): ReplCommand

.get, .post, .put, .patch and .delete — one command each, one implementation.

lib/commands/repl/commands/import.ts

Variables

v
command: ReplCommand

.import — a file brought into the page after the fact, under a name you can type.

lib/commands/repl/commands/imported.ts

Variables

v
command: ReplCommand

.imported — what each file in scope put there, painted by kind.

lib/commands/repl/commands/locals.ts

Variables

v
command: ReplCommand

.locals — what the stopped frame can see, which is not what the session declared.

lib/commands/repl/commands/next.ts

Variables

v
command: ReplCommand

.next — one step, over the call rather than into it. gdb's next.

lib/commands/repl/commands/nvim.ts

Variables

v
command: ReplCommand

.nvim — ../commands/open.ts, but in nvim rather than whatever $EDITOR says.

lib/commands/repl/commands/open.ts

Variables

v
command: ReplCommand

.open — one command doing what xdg-open does.

lib/commands/repl/commands/pwd.ts

Variables

v
command: ReplCommand

.pwd — the directory every relative path here is resolved against.

lib/commands/repl/commands/reload.ts

Variables

v
command: ReplCommand

.reload — the code again, your bindings gone.

lib/commands/repl/commands/request.ts

Functions

f
getHeaderName(word: string): string | null

The one header a headers.etag or headers[etag] asks for, or null where that is not what was said.

f
getHeaderType(word: string): "sent" | "received" | null

The side a headers.sent or header['received'] names, or null where it names neither.

f
parseRequestPart(asked: string): { kind: RequestPart; target: string; name?: string; }

The part a trailing word or two names, and whatever came before it.

f
printRequestReferenceNotFound(
repl: ReplContext,
reference: RequestReference
): void

Said in terms of HOW it was looked for: an id, a position and a URL each miss differently.

Variables

v
command: ReplCommand

.request — what a request actually was, after the fact.

lib/commands/repl/commands/save.ts

Variables

v
command: ReplCommand

.save — this session as a file you could run again.

lib/commands/repl/commands/scope.ts

Variables

v
command: ReplCommand

.scope — what this session has added to the page.

lib/commands/repl/commands/search.ts

Variables

v
command: ReplCommand

.search — which tests match, without running them.

lib/commands/repl/commands/step.ts

Variables

v
command: ReplCommand

.step — one step, into whatever the line calls. gdb's step.

lib/commands/repl/commands/tree.ts

Functions

f
drawTree(
repl: ReplContext,
root: string,
depth: number
): string

A directory drawn the way tree draws one, with the two sentences tree puts under it.

Variables

v
command: ReplCommand

.tree — a directory, drawn.

lib/commands/repl/commands/type.ts

Variables

v
command: ReplCommand

.type — what TypeScript would call it.

lib/commands/repl/commands/up.ts

Variables

v
command: ReplCommand

.up — toward whoever called this. Up the stack, which grows downwards.

lib/commands/repl/commands/url.ts

Variables

v
command: ReplCommand

.url — where the page is served, so you can open it yourself and watch.

lib/commands/repl/commands/version.ts

Variables

v
command: ReplCommand

.version — which qunitx this prompt is, for a bug report that names one.

lib/commands/repl/commands/vi.ts

Variables

v
command: ReplCommand

.vi — ../commands/open.ts, but in vi rather than whatever $EDITOR says.

lib/commands/repl/commands/view.ts

Variables

v
command: ReplCommand

.view — whatever the name turns out to be: a file numbered, a directory as a tree, or a value whole.

lib/commands/repl/commands/vim.ts

Variables

v
command: ReplCommand

.vim — ../commands/open.ts, but in vim rather than whatever $EDITOR says.

lib/commands/repl/completion.ts

Functions

f
complete(
server: REPLServer,
names: CompletionCache,
line: string,
callback: CompleterCallback,
cwd?: string
): void

What TAB offers: dot commands on a line that starts with one, page identifiers everywhere else.

f
completionCache(session: ReplSession): CompletionCache

Opens a cache over one session's completions, kept between keystrokes and refreshed behind them.

f
mutedSuggestionStyle(): string

The terminal escape sequence the GHOST SUGGESTION is drawn in — grey, so it reads as something offered rather than something typed.

f
pathBeingTyped(line: string): string | null

The path being typed on a .cat or .view line, or null on any other line.

f
pathsContinuing(
typed: string,
cwd: string
): string[]

Every path that continues typed, spelled the way it was — directories with a trailing slash.

f
pathSuggestion(
line: string,
cwd: string
): string

What to draw after the cursor on a path line: the rest of the shortest path that continues it.

f
setupSuggestionBehaviors(
server: REPLServer,
names?: CompletionCache,
cwd?: string
): () => string

zsh-style typeahead: the rest of the last matching line, greyed out after the cursor, Ctrl-F to take it.

Interfaces

I
CompletionCache

The page's identifiers, as something a keystroke can read.

Type Aliases

T
CompleterCallback = (
error: null,
result: [string[], string]
) => void

What node:repl hands a completer to answer through: the matches, and the word they finish.

lib/commands/repl/editor.ts

Functions

f
formatPathDisplay(
file: string,
projectRoot: string
): string

A path as a person would say it: qunitx-cli/lib/repl/session.ts for something in this project, and the absolute path for anything outside it.

f
opening(name: string): ReplCommand["main"]

.open, .edit and .e — whichever editor the environment prefers.

f
openingIn(editor: string): ReplCommand["main"]

What .open and every editor-named spelling of it do, differing only in which editor they mean.

f
pausePrompt(server: REPLServer): void

Stops the prompt reading, because what happens next is not the prompt's.

f
readIfThere(file: string): string | null

A file's contents, or null when it cannot be read — a missing path is an answer, not a crash.

f
replayableSource(lines: readonly string[]): string

The lines of a session worth replaying: everything typed, minus the shell escapes.

f
resumePrompt(server: REPLServer): void

Gives the prompt back its input — unless the session has gone.

f
userMeansYes(answer: string): boolean

How an answer to [Y/n] is read: Enter, or anything starting with y. Everything else is no.

f
whatToRun(edited: { text: string; saved: boolean; aborted: boolean; }): string

What an edit leaves to run: the buffer where it moved, and nothing where it did not.

f
writeIfPossible(
file: string,
contents: string
): boolean

True when the file was written. A save that cannot land is a message, not a crashed session.

lib/commands/repl/frames.ts

Functions

f
frameTable(
frames: readonly Repl.Frame[],
palette: Theme
): string

The stack as gdb prints one: newest first, numbered from where it stopped, with a mark on the frame being read. What .backtrace prints, and the sibling of scopeTable.

f
goBackToCallerFrame(
repl: ReplContext,
argument: string
): Promise<void>

.back — the same move as .up, under the word a hand reaches for.

f
goToCalleeFrame(
repl: ReplContext,
argument: string
): Promise<void>

.down — read the frame this one called, back toward where the page actually stopped.

f
goToCallerFrame(
repl: ReplContext,
argument: string
): Promise<void>

.up — read the frame that called this one. .up 2 for its caller's caller.

f
goToFrameNumber(
repl: ReplContext,
argument: string
): Promise<void>

.frame 1 — read a frame by its number, counting from where the page stopped.

f
sayWhichFrame(
repl: ReplContext,
argument: string
): Promise<void>

.here — say which frame is being read, and go nowhere.

f
showFrameSource(repl: ReplContext): Promise<void>

The source around wherever the page is stopped, drawn under whatever announced the stop.

f
stepIntoNextCall(
repl: ReplContext,
argument: string
): Promise<void>

.step — run one step, entering whatever this line calls. .step 3 for three.

f
stepOutOfThisFrame(
repl: ReplContext,
argument: string
): Promise<void>

.finish — run until this frame returns, and stop in whoever called it.

f
stepOverNextCall(
repl: ReplContext,
argument: string
): Promise<void>

.next — run one step, letting the next call run rather than entering it.

lib/commands/repl/history.ts

Functions

f
setupHistory(
server: REPLServer,
interactive: boolean
): void

Points history at ~/.qunitx_repl_history, honouring QUNITX_REPL_HISTORY (an empty value turns it off). Only in a terminal — setupHistory is a no-op without one, and a scripted invocation has no business writing to a history file.

f
trimHistoryFile(
file: string,
history: readonly string[]
): void

Cuts the history file back to the history it is meant to hold.

Variables

v
HISTORY_KEPT: 1000

How many lines a session keeps. node:repl keeps thirty; a shell keeps thousands.

lib/commands/repl/index.ts

Functions

f
agoInWords(milliseconds: number): string

How long ago, in the shortest true words: just now, 12s ago, 4m ago, 2h ago.

f
complete(
server: REPLServer,
names: CompletionCache,
line: string,
callback: CompleterCallback,
cwd?: string
): void

What TAB offers: dot commands on a line that starts with one, page identifiers everywhere else.

f
durationTone(milliseconds: number): Tone

Which tone a round trip wears. A tenth of a second is where a person starts to notice waiting, and a second is where they start doing something else.

f
formatPathDisplay(
file: string,
projectRoot: string
): string

A path as a person would say it: qunitx-cli/lib/repl/session.ts for something in this project, and the absolute path for anything outside it.

f
linesOf(source: NodeJS.ReadableStream): AsyncIterable<string>

The lines of source, ending when it does — including when it ended before anyone asked.

f
mutedSuggestionStyle(): string

The terminal escape sequence the GHOST SUGGESTION is drawn in — grey, so it reads as something offered rather than something typed.

f
pageGoneMessage(argv?: readonly string[]): string

What to SAY when the page has gone — the farewell, not the going.

f
printHeaders(
headers: ReadonlyMap<string, string>,
_palette: Theme
): string

A block of headers, one per line, with the names in the colour that means "this is what you came to find".

f
printRequest(
connection: HttpRequest,
palette: Theme
): string

Everything about one connection: what was sent, what came back, and the body.

f
printRequestLine(
connection: HttpRequest,
palette: Theme,
now: number
): string

One connection on one line, for a list of them: 2m ago #3 200 GET /api/users.

f
printStatus(
connection: HttpRequest,
palette: Theme
): string

The whole answer in one line: 200 OK · 101 bytes · 9ms | GET http://localhost:4000/api/users | #3.

f
printTarget(request: HttpRequest["request"]): string

What a request was for: GET http://localhost:4321/api/users.

f
renamedNamespaces(loaded: ReadonlyArray<readonly [string, readonly string[]]>): string[]

Preloads that did not get the name they asked for, and what they answer to instead.

f
replayableSource(lines: readonly string[]): string

The lines of a session worth replaying: everything typed, minus the shell escapes.

f
run(): Promise<number>

Runs qunitx repl: opens a browser page, then reads, evaluates and prints in it until the input ends. Resolves with the process exit code once everything is closed.

f
setupLineHighlighting(
server: REPLServer,
palette: Theme
): void

Paints the line AS IT IS TYPED, in the colours the theme gives each capture.

f
setupRightMarginPreview(
server: REPLServer,
session: ReplSession,
arg_2: PreviewRoom
): void

Shows what the line would come to, dimmed against the RIGHT MARGIN while it is still being typed.

f
setupSuggestionBehaviors(
server: REPLServer,
names?: CompletionCache,
cwd?: string
): () => string

zsh-style typeahead: the rest of the last matching line, greyed out after the cursor, Ctrl-F to take it.

f
shell(
command: string,
out: NodeJS.WritableStream,
cwd: string
): Promise<number>

Runs one shell command, streaming its output to the terminal as it arrives.

f
sizeTone(bytes: number): Tone

Which tone a body size wears. A terminal stops being comfortable around a hundred kilobytes and stops coping around a megabyte, so those are the two boundaries.

f
statusTone(status: number): Tone

Which tone a status wears — the one place the four classes are decided.

f
trimHistoryFile(
file: string,
history: readonly string[]
): void

Cuts the history file back to the history it is meant to hold.

f
userMeansYes(answer: string): boolean

How an answer to [Y/n] is read: Enter, or anything starting with y. Everything else is no.

f
whatToRun(edited: { text: string; saved: boolean; aborted: boolean; }): string

What an edit leaves to run: the buffer where it moved, and nothing where it did not.

f
withoutTerminalReports(chunk: Buffer): Buffer

The same bytes with the terminal's answers to ITSELF dropped: mouse and cursor-position reports.

f
withVimHistoryKeys(stdin: NodeJS.ReadStream): NodeJS.ReadStream

stdin, with Ctrl-K and Ctrl-J walking history — a NEW stream, which is what to read instead.

lib/commands/repl/no-such-value-line.ts

Functions

f
noSuchValueLine(
command: string,
argument: string
): string

One line saying why a command has no value to show, in the two ways there can be none: you named nothing, or you named something this session does not have.

lib/commands/repl/open-in-browser.ts

Functions

f
openInBrowser(address: string): Promise<string | null>

Hands an address to whatever this desktop opens addresses with — the browser already running.

lib/commands/repl/path-argument.ts

Functions

f
findPath(
typed: string,
cwd: string
): FoundPath

Asks the filesystem what is at a typed path — one of four answers, and never a thrown error.

f
getPathAndDepth(argument: string): { file: string; depth: number; }

The path a command was pointed at, and how deep it was asked to go.

Type Aliases

lib/commands/repl/print-request.ts

Functions

f
agoInWords(milliseconds: number): string

How long ago, in the shortest true words: just now, 12s ago, 4m ago, 2h ago.

f
durationTone(milliseconds: number): Tone

Which tone a round trip wears. A tenth of a second is where a person starts to notice waiting, and a second is where they start doing something else.

f
paintVerb(verb: string): string

The colour a verb wears, wherever a verb is printed — the reading ones cool, the writing ones warm, and the one that destroys something red. The same five every HTTP tool has taught.

f
printHeaders(
headers: ReadonlyMap<string, string>,
_palette: Theme
): string

A block of headers, one per line, with the names in the colour that means "this is what you came to find".

f
printRequest(
connection: HttpRequest,
palette: Theme
): string

Everything about one connection: what was sent, what came back, and the body.

f
printRequestLine(
connection: HttpRequest,
palette: Theme,
now: number
): string

One connection on one line, for a list of them: 2m ago #3 200 GET /api/users.

f
printStatus(
connection: HttpRequest,
palette: Theme
): string

The whole answer in one line: 200 OK · 101 bytes · 9ms | GET http://localhost:4000/api/users | #3.

f
printTarget(request: HttpRequest["request"]): string

What a request was for: GET http://localhost:4321/api/users.

f
sizeTone(bytes: number): Tone

Which tone a body size wears. A terminal stops being comfortable around a hundred kilobytes and stops coping around a megabyte, so those are the two boundaries.

f
statusTone(status: number): Tone

Which tone a status wears — the one place the four classes are decided.

Type Aliases

T
Tone = "good" | "warn" | "info" | "bad"

What a number or a status is worth saying about: the vocabulary every colour here is chosen from.

Variables

v
MAX_BODY_PREVIEW_LINES_COUNT: 40

How much of a body a request command prints before it starts saying "there is more".

lib/commands/repl/value-details.ts

Functions

f
printValueDetails(
repl: ReplContext,
argument: string
): Promise<string | null>

The whole of a value: a function's entire declaration, body included, or anything else rendered all the way down rather than summarised to a line. null where there is no such name. What .view prints.

f
summarizeValue(
repl: ReplContext,
argument: string
): Promise<string | null>

Enough to recognise a value by: where it is written, what was written above it, and its signature. null where this session knows nothing by that name. What .doc prints.

lib/commands/run.ts

Functions

f
run(
entry: string,
settings?: ScriptSettings
): Promise<ScriptOutcome>

Runs a script file in a real browser: bundles it with esbuild, serves it from a localhost origin, evaluates it as a module in the page, and streams its console output to this terminal. Resolves once the script's top level — including any top-level await — settles.

f
setup(
argv?: string[],
cwd?: string
): Task<ScriptInvocation, ScriptConfigFailure>

Reads qunitx run's argv into a target and its settings.

f
suiteHint(
cwd: string,
entry: string,
relativeTo?: (
from: string,
to: string
) => string
): string

The qunitx <file> line the suite warning suggests, with forward slashes.

f
watchDirectoriesFrom(
inputs: string[],
cwd: string,
pathApi?: Pick<path, "resolve" | "dirname">
): string[]

The directories watch mode should watch, from esbuild's metafile input keys.

f
watchLoop(
directories: string[],
run: () => Promise<void>,
watch?: (
directory: string,
listener: () => void
) => void
,
debounceMs?: number
): Promise<never>

Runs run once, then again on every change to a first-party file in the bundle. Never resolves — watch mode ends when the process is signalled — except to REJECT if the very first run throws, which is a script that never started rather than a bad edit to recover from.

Interfaces

I
ScriptConfig

Everything one qunitx run invocation needs. Its own type rather than the test runner's Config: a script has no test files, no filter, no reporter and no output directory, and threading a script mode through those would make every one of them mean two things.

I
ScriptInvocation

What qunitx run's argv amounts to: the one target, and the settings the flags asked for.

I
ScriptOutcome

What one execution of the script produced.

  • browserLogs: BrowserLog[]

    The script's own console calls and uncaught errors, in emit order and capped at MAX_BROWSER_LOGS — the same shape and the same cap a test run reports, because it is the same thing: a page's console, read over CDP.

  • browserLogsDropped: number

    How many were dropped to stay under the cap. 0 when nothing was.

  • entry: string

    Absolute path of the file that ran, resolved against cwd.

  • exitCode: number

    globalThis.exitCode if the script set one, 1 if it threw, else 0.

  • tests: RunResult | null

    The suite the entry declared, or null when it was a plain script.

  • value: unknown

    The script's export default, or undefined when it has none — or when it had one that could not be handed back, in which case ScriptOutcome.valueProblem says so.

  • valueProblem: string | null

    Why ScriptOutcome.value is undefined despite the script exporting something, or null when there is nothing to explain.

I
ScriptSettings

Everything a caller may set on a script run: the CLI's flags and the API's options, minus argv.

Type Aliases

Variables

v
ScriptBuildFailed: Failure.FailureFactory<"ScriptBuildFailed", { entry: string; }>

The script could not be bundled — a syntax error, or an import that does not resolve.

v
ScriptNotFound: Failure.FailureFactory<"ScriptNotFound", { entry: string; }>

qunitx run was pointed at a file that is not there.

lib/commands/search.ts

Functions

f
run(config: Config): Promise<number>

--search / -s / --print / --preview: list the tests the current selection matches, without running them.

f
scan(config: Config): Promise<SearchReport>

Scans the selected files and resolves which of their tests the current selection matches — no browser, no bundle, no execution.

Interfaces

I
FoundTest

One test found by the static scan, named exactly as QUnit would name it.

I
SearchReport

What the static scan found, before anything is printed.

I
UnlistableCounts

Why some declarations could not be listed, split by cause.

lib/commands/test.ts

Functions

f
run(config: Config): Promise<RunOutcome>

Runs the whole suite once in headless Chrome and resolves with its RunOutcome.

f
scriptHint(
cwd: string,
file: string,
relativeTo?: (
from: string,
to: string
) => string
): string

The qunitx run <file> line a zero-test run suggests, with forward slashes.

f
watch(config: Config): Promise<WatchSession>

Starts a watch session: one browser, one page, every test file in a single bundle, behind an HTTP server that stays up. Resolves once the initial run has finished and the watchers are armed — the returned WatchSession is how the caller stops it again.

Interfaces

I
RunOutcome

How a one-shot run ended. Everything a caller needs to decide what happens next, and nothing about how it should be announced — run neither writes a summary nor exits.

  • durationMs: number

    Wall-clock duration of the test phase in milliseconds.

  • exitCode: number

    0 when every test passed, 1 for any failure, group rejection, or empty filtered run.

  • finishedAt: number

    Epoch ms when it ended. finishedAt - startedAt is durationMs modulo clock resolution.

  • startedAt: number

    Epoch ms when the test phase began — after the bundle, at the first navigation.

I
WatchSession

A live watch session: the run's browser, server and file watchers, kept open.

lib/commands/test/grouping.ts

Functions

f
applyWatchLineTargets(config: Config): Promise<void>

Watch-mode line targets: narrow fsTree to the targeted files and apply their selectors for the whole session.

f
resolveTargetedFiles(
config: Config,
allFiles: string[]
): Promise<TargetedFile[]>

Resolves each file#34 input into the selectors for that file, dropping targets whose file is no longer in the run (a glob, --changed or --only-failed may have filtered it out) and those that resolved to nothing — both fall back to running the file whole, which is what a null selectors means. Every warning is surfaced; a line target that quietly did not narrow is worse than one that says so.

f
splitIntoGroups(
files: string[],
groupCount: number,
timings: Record<string, number>
): Promise<{ groups: string[][]; weights: Map<string, number>; }>

Packs files into groupCount groups of roughly equal estimated duration, returning the groups and the per-file weights the estimate used (the caller reuses them to apportion wall time).

lib/commands/test/remote-pages.ts

Functions

f
runRemotePages(
config: Config,
urls: string[],
browserPromise: Promise<Browser>
): Promise<RunOutcome>

Runs every remote QUnit page in the config, one after another, and reports as one run.

Variables

v
RemotePageMixedWithOtherInputs: Failure.FailureFactory<
"RemotePageMixedWithOtherInputs",
{ page: string; alongside: number; }
>

A run was given a remote QUnit page and something else as well — a file, or another URL.

lib/commands/test/tests-in-browser.ts

Functions

f
armJSCoverage(
page: Page,
config: Config,
log?: (...args: unknown[]) => void
): Promise<boolean>

Arms V8 line coverage on the page before navigation, returning whether it started. resetOnNavigation: false keeps the data across the goto below, so the bundle's execution is captured. Chromium-only — run.ts already disables coverage for firefox/webkit, and page.coverage exists only on chromium pages.

f
buildAllGroupBundles(groupConfigs: Config[]): Promise<void>

Builds all concurrent group bundles with a single esbuild invocation.

f
buildTestBundle(config: Config): Promise<void>

Pre-builds the esbuild bundle for all test files and caches the result in the group's build state.

f
bundleCacheKey(
opts: esbuild.BuildOptions,
files: string[]
): string

Cache key for the daemon/watch incremental esbuild context. Single source of truth for what makes two builds interchangeable: file set + every BuildOption that varies between runs. Items intentionally NOT keyed: plugins (daemon shuts down on package.json mtime change; the prepended qunitxRuntimePlugin is static and deterministic), nodePaths (cwd-bound, daemon stays in cwd), and the hardcoded literals (bundle, keepNames, legalComments, jsx, sourcemap, footer, logLevel). When adding a new variable BuildOption, extend this function — that is the contract enforced by the unit suite.

f
deriveBuildErrorType(error: unknown): string

Derives a human-readable error category from an esbuild BuildFailure or a generic Error. Inspects the first structured esbuild message when available; falls back to string heuristics.

f
flushConsoleHandlers(
handlers?: Set<Promise<void>> | null,
page?: Page | null,
deadline?
): Promise<void>

Awaits all in-flight console handler promises until the Set is stably empty, recursing to catch handlers added by Firefox BiDi events that arrive during each await. The deadline (default 2 s) guards against infinite recursion.

f
formatBuildErrors(error: unknown): string

Formats esbuild BuildFailure messages into clean human-readable text (no ANSI codes). When given a structured BuildFailure, each error is formatted with its file location and a caret line. Falls back to stripping ANSI codes from the error's string representation.

f
reconcileUndeliveredResults(
counter: Counter,
result: QUnitResult
): number

Reconciles the Node-side counter with QUnit's authoritative in-page tally after a run finishes, for a single-group run. Returns how many finished results the WebSocket stream failed to deliver (0 on a clean run).

f
run(
config: Config,
connections: Connections,
targetTestFilesToFilter?: string[] | null
): Promise<Connections | undefined>

Runs the esbuild-bundled tests inside a Playwright-controlled browser page and streams TAP output.

Type Aliases

T
BrowserRunOutcome =
{ kind: "completed"; }
| { kind: "empty"; }
| { kind: "no-tests-ran"; }
| { kind: "stalled"; }

How a browser run ended, decided from QUnit's tally plus whether a WS done arrived.

lib/commands/test/timings.ts

Functions

f
compute(
groups: string[][],
weights: Map<string, number>,
wallTimes: Map<number, number>
): Map<string, number>

Distributes each group's wall-clock ms to its files proportionally by LPT weight.

f
persist(
fileTimes: Map<string, number>,
projectRoot: string
): Promise<void>

Writes the merged per-file timings back to tmp/test-timings.json for the next run to pack with.

f
print(
fileTimes: Map<string, number>,
projectRoot: string
): void

--debug listing of this run's per-file wall times, slowest first.

f
read(projectRoot: string): Task<Record<string, number>, never>

Reads tmp/test-timings.json from projectRoot; returns {} on any error or invalid content.

lib/commands/upgrade/channel.ts

Functions

f
detect(probe?: ChannelProbe): InstallChannel

Resolves the channel this process is running from.

f
isSelfUpdatable(channel: InstallChannel): boolean

Whether this channel's updater is qunitx's to run.

f
updateArgv(
channel: InstallChannel,
version: string,
registry?: "npm" | "jsr"
): string[]

The command that upgrades this channel: deno install, npm install -g, deno add, git pull.

Interfaces

I
ChannelProbe

The observable state detect reads. Every field defaults to this process, and every field is injectable so each channel is reachable in a test without that install existing.

Type Aliases

lib/commands/upgrade/index.ts

Functions

f
parseArgs(argv: string[]): Result<UpgradeOptions, Failure.Of<InvalidArgument>>

Parses qunitx upgrade's arguments. A bare 0.34.2 (or v0.34.2) pins the version, the same as --version=0.34.2.

Interfaces

I
UpgradeDeps

The seams run reaches the world through: the channel it believes it is, the release lookup, the installer, and where its text goes. Every one defaults to the real thing.

I
UpgradeOptions

What qunitx upgrade's argv asked for.

Variables

lib/commands/upgrade/install.ts

Functions

f
apply(
plan: InstallPlan,
deps?: InstallDeps
): Promise<string[]>

Downloads the release archive, verifies it against the release's checksums.txt, and replaces the running binary (and its esbuild sidecar, when the install has one).

f
extract(
archivePath: string,
destination: string
): Promise<void>

Unpacks a release archive with the host's tar / unzip, falling back to PowerShell's Expand-Archive where a Windows shell has no unzip — the same two-step jsr/cli.ts uses, for the same reason: no JS extractor has to ship inside the binary.

f
swapBinary(
staged: string,
target: string,
platform?: NodeJS.Platform
): Promise<void>

Puts staged at target, honouring the one platform rule that matters: POSIX replaces a running executable with an atomic rename (the running process keeps its inode), while Windows cannot unlink a running image at all — so the old file is renamed aside first, and renamed back if anything goes wrong.

Interfaces

I
InstallDeps

The seams apply reaches the world through. All three default to the real thing.

I
InstallPlan

What to install, and over what.

Variables

v
AssetMissing: Failure.FailureFactory<"UpgradeAssetMissing", { asset: string; tag: string; }>

The release has no asset for this platform — a deno compile build asking for a target only the SEA matrix publishes, or the reverse.

v
ChecksumMismatch: Failure.FailureFactory<
"UpgradeChecksumMismatch",
{ asset: string; expected: string; actual: string; }
>

The downloaded bytes do not hash to what the release published — a corrupted or truncated transfer, or a tampered asset. Nothing has been replaced when this is thrown.

v
ChecksumsMissing: Failure.FailureFactory<"UpgradeChecksumsMissing", { tag: string; }>

The release publishes no checksums.txt, so the download cannot be verified. Refused rather than downgraded to a warning: an unverified binary replacing the one you are running is exactly the thing worth refusing.

v
DownloadFailed: Failure.FailureFactory<"UpgradeDownloadFailed", { url: string; reason: string; }>

The download itself failed — an HTTP error or a dead connection mid-transfer.

v
ReplaceFailed: Failure.FailureFactory<
"UpgradeReplaceFailed",
{ target: string; reason: string; recovered?: string; }
>

The replace itself failed, after the new binary was downloaded and verified. Carries the recovery path when the old binary had to be moved aside first.

v
TargetNotWritable: Failure.FailureFactory<"UpgradeTargetNotWritable", { dir: string; }>

The directory holding the binary is not writable by this user — the usual case being a root-owned prefix such as /usr/local/bin.

lib/commands/upgrade/manifest.ts

Functions

f
bump(
manifestPath: string,
version: string
): Promise<BumpedEntry>

Points the manifest's qunitx-cli entry at version, keeping whatever range operator it already used: ^ stays ^, ~ stays ~, and an exact pin stays exact.

f
registry(manifestPath: string): Promise<"npm" | "jsr">

Which registry a deno manifest pins qunitx-cli through, so a refusal names the deno add that updates the entry already there instead of adding a second one beside it. npm when the file says nothing, matching what deno add qunitx-cli resolves to.

Interfaces

I
BumpedEntry

What bump changed: where the entry lives, and the range it now carries.

  • field: string

    The part of the manifest that changed: imports, dependencies or devDependencies.

  • range: string

    The range now written there, operator included.

Variables

v
EntryMissing: Failure.FailureFactory<"UpgradeManifestEntryMissing", { manifest: string; }>

A manifest that declares qunitx-cli, but not at a version this can bump — an unpinned specifier, or a dependency that is not declared there at all.

lib/commands/upgrade/process.ts

Functions

f
spawn(argv: string[]): Promise<SpawnResult>

Runs another tool's installer — deno install, npm install -g — and reports how it went.

Interfaces

I
SpawnResult

How the installer this process ran finished — shaped like the process it describes.

lib/commands/upgrade/release.ts

Functions

f
assetName(
flavor: "sea" | "deno",
platform?: NodeJS.Platform,
arch?: string,
libc?: Libc
): string | null

The release asset that replaces a running binary of this flavor, or null when this platform has no published build — the SEA matrices cover five targets, the deno one five.

f
compare(
a: string,
b: string
): number

Compares two x.y.z versions the way a release line orders them: negative when a is older, 0 when they are the same, positive when a is newer.

f
find(
version?: string,
fetchImpl?: fetch
): Promise<Release>

Fetches the newest release, or the one matching version when pinned.

f
hostLibc(): Libc

The C library this process runs on. Node's report names the glibc version it found at runtime, and leaves it out on musl.

f
parseChecksums(text: string): Map<string, string>

Parses the release's checksums.txt (sha256sum output) into name → sha256.

Interfaces

I
Release

A published release, reduced to what an upgrade needs.

I
ReleaseAsset

One downloadable file on a release.

Type Aliases

T
Libc = "glibc" | "musl"

Which C library a Linux binary links; everywhere else it is glibc by convention.

Variables

v
LookupFailed: Failure.FailureFactory<"UpgradeLookupFailed", { url: string; reason: string; }>

The release lookup could not be answered — no network, a proxy, an HTTP error, or GitHub's unauthenticated rate limit.

lib/console.ts

Functions

f
streamConsole(
stdout: { write(text: string): unknown; },
stderr?: { write(text: string): unknown; }
): Console

Adapts anything with a write(string) — a node:fs stream, a socket, an array-backed fake — into a Console. stderr defaults to stdout, so one stream collects the whole run.

Interfaces

I
Console

Where a run's text goes: console, made injectable.

Variables

v
processConsole: Console

The CLI's: the real process streams. .write is looked up per call, so the daemon's stdout interception still reaches it.

v
silentConsole: Console

Discards everything. The JS API's default, so a programmatic run prints nothing unless it was asked to.

lib/coverage/collect.ts

Functions

f
collect(
config: Config,
entries: V8ScriptCoverage[]
): Promise<void>

Merges one page's stopJSCoverage() result into config.state.results.coverage. No-op unless coverage is enabled and a source-map decoder is present. Only the test bundle is attributed; node_modules sources are dropped here (test entry files are dropped later, in the report layer).

lib/coverage/index.ts

Functions

f
collect(
config: Config,
entries: V8ScriptCoverage[]
): Promise<void>

Merges one page's stopJSCoverage() result into config.state.results.coverage. No-op unless coverage is enabled and a source-map decoder is present. Only the test bundle is attributed; node_modules sources are dropped here (test entry files are dropped later, in the report layer).

f
Report.buildHtml(rows: FileRow[]): string

Builds a self-contained HTML report: a summary table plus per-file source with line coloring.

f
Report.buildLcov(rows: FileRow[]): string

Builds a standard LCOV lcov.info string (line coverage only: DA/LF/LH per file).

f
Report.buildRows(
collector: CoverageFileMap,
testFiles: Set<string>,
projectRoot: string
): FileRow[]

Turns the raw coverage map into sorted, test-file-filtered rows with computed percentages.

f
Report.write(
config: Config,
testFiles: string[]
): Promise<void>

Renders the run's accumulated coverage: always prints the terminal summary, then writes the lcov/html reports the user requested via config.coverageFormats. testFiles (the run's test entry paths) are excluded so the report reflects the code under test, not the tests.

Namespaces

N
Report

Coverage report rendering — writes the report and builds its rows/lcov/html forms.

lib/coverage/report.ts

Functions

f
buildHtml(rows: FileRow[]): string

Builds a self-contained HTML report: a summary table plus per-file source with line coloring.

f
buildLcov(rows: FileRow[]): string

Builds a standard LCOV lcov.info string (line coverage only: DA/LF/LH per file).

f
buildRows(
collector: CoverageFileMap,
testFiles: Set<string>,
projectRoot: string
): FileRow[]

Turns the raw coverage map into sorted, test-file-filtered rows with computed percentages.

f
write(
config: Config,
testFiles: string[]
): Promise<void>

Renders the run's accumulated coverage: always prints the terminal summary, then writes the lcov/html reports the user requested via config.coverageFormats. testFiles (the run's test entry paths) are excluded so the report reflects the code under test, not the tests.

lib/repl/docs.ts

Functions

f
blockAt(
source: string,
line: number
): string

A whole declaration — its first line through the } that closes it.

f
commentAbove(
source: string,
line: number
): string[]

The comment written directly above a line, with its markers taken off.

f
renderDoc(
lines: readonly string[],
palette: Theme
): string

A comment as the prompt should print it: prose muted, and the examples in it read as code.

f
signature(
source: string,
line: number
): string

The declaration a line starts, up to where its body begins.

lib/repl/excerpt.ts

Functions

f
excerpt(
source: string,
line: number,
palette: Theme,
limits?: Limits
): string

The source around a breakpoint, numbered, with a mark on the line the page stopped on.

f
limits(): Limits

How much to show, with anything the developer has said on top.

Interfaces

I
Limits

How much source to show around a breakpoint.

  • after: number

    Lines after it, at most. It stops sooner at a return.

  • before: number

    Lines before it, at most. It stops sooner at the start of the enclosing block.

Variables

v
LINES: Limits

The default window: enough of the way up to see what function you are in, and enough of the way down to see what it is about to answer.

lib/repl/highlight.ts

Functions

f
depth(source: string): number

How many brackets are open at the end of the source — what a continuation prompt counts.

f
highlight(
source: string,
palette: Theme
): string

Source as the terminal should draw it: every token wrapped in its capture's style.

f
tokenize(source: string): Token[]

The source, split into what nvim would capture.

Interfaces

I
Token

A run of source, and what nvim's treesitter queries would capture it as.

lib/repl/http-service.ts

Functions

f
create(): HttpService

An empty store — one per session.

f
formatBytes(bytes: number): string

A byte count as a person would read it: 0 bytes, 812 bytes, 4.2 KB, 1.3 MB.

f
isTextual(contentType: string): boolean

Whether a content type is something a terminal should be asked to print.

f
isValidHeaderName(name: string): boolean

Whether a name is one HTTP will carry — the token characters RFC 9110 allows, and no others.

f
parseHeader(text: string): { name: string; value: string; } | null

A header as it was typed, or null where that is not a header at all.

f
parseRequestKind(target: string): RequestReference

Which request an argument names: a name (#3), a position counted back (2), or a URL.

f
peekRequest(
httpService: HttpService,
verb: string | null,
url: string,
pageUrl?: string | null
): HttpRequest | null

The most recent request a [verb:]url means, or null where the session never made one.

f
pickRequest(
httpService: HttpService,
asked: RequestReference,
pageUrl: string | null
): HttpRequest | null

The request a RequestReference picks out, or null where the session made no such one.

f
resolveUserInputURL(
target: string,
pageUrl: string | null
): string | null

The absolute URL a target means, or null where it means nothing usable.

f
setQueryParams(
url: string,
params: Readonly<Record<string, unknown>>
): string

url, with params folded into its query: { page: 2 } on /api/users is /api/users?page=2.

f
setUserAgent(
httpService: HttpService,
session: { toJSON(expression: string): Promise<string | null>; }
): Promise<void>

Replaces the default user-agent with the page's own, which is the one a server should see.

f
store(
httpService: HttpService,
request: HttpRequest
): void

Files a request, newest last, naming it as it goes and dropping the oldest once there are REQUESTS_KEPT of them.

Interfaces

I
HttpRequest

One request and whatever became of it.

  • at: number

    When it was made, for ordering and for saying how long ago.

  • failed: string | null

    Why there is no reply, said the way it would be said out loud. null where there is one.

  • id: number

    What this request is called for the rest of the session: #3, and .request #3 afterwards.

  • ms: number

    Wall-clock milliseconds, rounded — a REPL has no use for the fractions.

  • request: Request

    The request, exactly as it was made.

  • response: Response | null

    null where it failed before a reply — DNS, connection refused, timeout.

I
HttpService

A session's HTTP state: the headers it will send next, and everything it has sent.

  • headers: Map<string, string>

    Saved for every upcoming request, lower-cased. .header list prints this.

  • lastId: number

    The last id handed out. Kept here rather than counted from requests.length, which stops being the number of requests made the moment the cap drops the oldest one — and an id that starts repeating is worse than no id at all.

  • requests: HttpRequest[]

    Oldest first, newest last, capped at REQUESTS_KEPT.

  • scratchpadText: string

    The body buffer .post … : opens: one for the life of the session, so reopening it continues the same thought rather than starting a blank one — the request equivalent of the scratchpad a prompt keeps. Saved empty is how it is cleared.

I
Request

What was actually handed to fetch — the request, as this session made it.

I
Response

What came back. Absent on an HttpRequest that never got a reply.

Type Aliases

T
RequestReference =
{ kind: "id"; id: number; }
| { kind: "position"; back: number; }
| { kind: "url"; verb: string | null; url: string; }

How a request was asked for — what parseRequestKind read, and what a miss has to explain.

Variables

v
DEFAULT_HEADERS: ReadonlyMap<string, string>

What a session sends before you have said anything about headers.

v
REQUESTS_KEPT: 200

How many requests a session keeps.

lib/repl/input.ts

Functions

f
candidates(input: string): string[]

The expressions to try for one line of input, in order.

f
declaration(input: string): Declaration | null

What a line declares, or null where it declares nothing.

f
importStatement(input: string): ImportStatement | MisreadImport | null

An import declaration, read into what it binds — or null for anything else.

f
isIncomplete(description: string): boolean

Whether a SyntaxError means the input is unfinished rather than wrong.

Interfaces

I
Binding

What one name in an import statement binds, and where in the module it comes from.

I
Declaration

A declaration made at a breakpoint: what it binds, to what, and how long it should last.

I
ImportStatement

An import statement typed at the prompt: what it reads, and what it binds.

  • bindings: Binding[]

    Every name it puts in scope. Empty for import './styles.css', which binds nothing.

  • specifier: string

    The module it reads, exactly as written — a path, or a package name.

I
MisreadImport

An import statement that could not be read, and the line that would have been read.

lib/repl/navigate.ts

Functions

f
navigate(
page: { goto(url: string): Promise<unknown>; },
url: string,
arg_2?: { backoffMs?: number; }
): Promise<void>

Opens the session's page, going again when the OS was briefly out of socket buffers.

lib/repl/proxy.ts

Functions

f
proxyTo(upstream: string): Promise<Proxy>

A WebSocket the DevTools frontend is allowed to open, piped to the one Chrome will accept.

Interfaces

I
Proxy

A running proxy: the port a frontend may connect to, and how to take it down.

lib/repl/scope.ts

Functions

f
scopeTable(
entries: readonly ScopeEntry[],
width: number
): string

A scope as a human reads it: one name per line, its value beside it, where it came from after.

lib/repl/session.ts

Functions

f
moduleNameFor(file: string): string

The name a file goes into scope under when nobody says what to call it.

f
namespacesFor(found: readonly string[]): Map<string, string>

What each preload goes into scope as, with two that want one name pulled apart rather than left to overwrite each other.

f
preferredFirst(
found: readonly string[],
asked: readonly string[],
cwd: string
): string[]

The preload list with, in each group of files that want one name, the one that should keep it moved to the front.

f
resolvePreload(
config: Config,
inputs: readonly string[]
): Promise<string[]>

The files a qunitx repl <inputs> invocation should preload, resolved the way a run resolves its targets — directories walked, globs expanded, extensions honoured.

f
start(
config: Config,
preload?: string[],
onOpen?: (session: ReplSession) => void
): Promise<ReplSession>

Starts a REPL for an assembled config: bundles qunitx and the preload files, serves it, opens a page on it, and attaches to that page over CDP.

Interfaces

I
Breakpoint

A breakpoint the session set, numbered the way gdb numbers them.

I
Frame

One frame of a stopped call stack.

  • index: number

    Its place on the stack: 0 is where the page stopped, and each one after it is the caller.

  • selected: boolean

    Whether the rest of the session is currently reading this one.

  • where: string

    Where it is, the same way a pause names itself.

I
ReplResult

What one input produced. output is already rendered — the terminal prints it verbatim — and the rest is there so a caller can react rather than parse.

  • failed: boolean

    Whether the input worked. What output never says, and what a caller needs before it can do anything but print: colour the line red, decline to add it to the replayable history, exit non-zero on a piped session.

  • incomplete: boolean

    The input was unfinished (const a = {), so nothing ran and the terminal should read on.

  • output: string

    The text to print, whichever way it went: the rendered value when the input worked, the error and its source-mapped stack when it did not. Empty when there is nothing to print.

  • pausedAt: string

    Where the input stopped at a debugger statement, or absent when it ran to completion.

  • tests: TestDetails[]

    Tests QUnit ran because of this input. Already reported through the session's reporters.

  • thrown: boolean

    The failure was the page throwing, rather than the session refusing the input.

I
ReplSession

A live REPL: one browser page, kept open, that evaluates what you type.

I
ScopeEntry

One thing in scope: what it is called, what it holds, and where it came from.

Type Aliases

T
ReplStartFailure =
Failure.Of<UnsupportedBrowser>
| Failure.Of<PreloadBuildFailed>

Everything starting a REPL can fail with, beyond config assembly.

T
StepKind = "into" | "over" | "out"

One step, named the way gdb names it: step, next, finish.

Variables

v
UnsupportedBrowser: Failure.FailureFactory<"UnsupportedBrowser", { browser: string; }>

The REPL evaluates over the Chrome DevTools Protocol, which firefox and webkit do not speak.

lib/repl/suggest.ts

Functions

f
split(line: string): Typed | null

Where a completion would go in line, or null where none could.

f
suggest(
line: string,
sources: Sources
): string

The rest of what is being typed, drawn after the cursor — zsh's autosuggestion.

Interfaces

I
Sources

What a suggestion can be drawn from. Both are optional: either alone still answers.

I
Typed

A name and a path: document.qu is qu hanging off document, doc is doc off nothing.

  • base: string

    The expression whose properties could finish the token, or '' for a bare name.

  • token: string

    The partial identifier under the cursor, empty directly after a dot.

lib/repl/terminal.ts

Functions

f
plain(text: string): string

The text with its colour taken off — what the terminal would show on a screen with none.

f
plainLength(text: string): number

Width as the terminal sees it — colour codes take columns nowhere but in the string.

f
terminalWidth(output: NodeJS.WritableStream): number

How wide a line may be. 80 where nothing says — a pipe has no width, and neither does a file.

f
truncate(
text: string,
width: number
): string

Cuts a line to width columns, counting what the terminal counts.

Variables

v
ESCAPE

The character every terminal control sequence begins with.

lib/repl/theme.ts

Functions

f
ansiStyle(spec: string): string

One style spec as an SGR prefix — fg=yellow, fg=8, fg=#e5c07b, fg=red,bold.

f
theme(colour?: boolean): Theme

The palette to draw with: the defaults, with anything the developer has set on top.

Interfaces

I
Theme

A palette, keyed by the capture names nvim's treesitter highlighting uses.

lib/repl/type-of-value.ts

Functions

f
typeOfValue(
value: unknown,
depth?: number
): string

A TypeScript type for a runtime value — typeof, for people who want an answer.

lib/reporters/dot.ts

Classes

c
DotReporter

One character per test, failures reported in full at the end. The right shape for large suites and CI logs, where a line per test is thousands of lines of noise but you still want live progress.

lib/reporters/failure.ts

Functions

f
extractStackAt(stack: string | null | undefined): string | null

Extracts the source location from a stack trace string. Supports Chrome/Node style "at func (url:line:col)" and Firefox/WebKit style "@url:line:col". Returns a clean location string without surrounding parens, or null if nothing can be extracted.

f
failedAssertions(
details: TestDetails,
decoder?: SourceMapDecoder | null,
projectRoot?: string
): FailureInfo[]

Extracts every genuinely-failing assertion (todo assertions are expected to fail and are excluded) from a testEnd payload, resolving stacks back to original sources.

f
parseAt(at: string | null): { file: string; line: number; col: number; } | null

Splits an at string (path:line:col) into parts; returns null when it isn't a location.

Interfaces

I
FailureInfo

One failing assertion, normalized for rendering. Every reporter needs the same three things — what failed, where in the original source, and the values involved — so the source-map resolution and value normalization happen once here rather than per reporter.

lib/reporters/github.ts

Classes

c
GithubReporter

GitHub Actions reporter: spec output, plus a ::error workflow command per failure so the failure is annotated inline on the PR diff.

Functions

f
annotation(
title: string,
failure: FailureInfo
): string

Builds one ::error file=…,line=…,col=…,title=…::message workflow command.

lib/reporters/index.ts

Functions

f
browserLog(
config: Config,
log: BrowserLog
): void

Emits one console.* call or uncaught error from the page under test. Rendered verbatim — no # prefix — because it is the page's output, not qunitx's, and prefixing it would corrupt whatever the page was deliberately printing.

f
create(config: Config): Reporter[]

Reporter wiring. config.reporter selects exactly one stdout reporter; artifact reporters (JUnit) are additive and stack on top. Built once per run in Config.setup and shared by every concurrent group — the group configs are spread off the parent config, so they all reference this same array (the same way the run counter is shared).

f
error(
config: Config,
message: string,
options?: NoticeOptions
): void

A diagnostic that also belongs on stderr — a stack trace, a timeout, a page that crashed.

f
info(
config: Config,
message: string,
options?: NoticeOptions
): void

A decision the runner made and wants on the record — what it chose to run, what it skipped.

f
runEnd(
config: Config,
info: RunEndInfo
): Promise<void>

Emits run end to every active reporter, awaiting any that flush asynchronously.

f
runStart(
config: Config,
info: RunStartInfo
): void

Emits run start to every active reporter. In watch mode this fires once per rerun.

f
testEnd(
config: Config,
details: TestDetails
): void

Applies one testEnd to the counters, then fans it out to every active reporter. The counter update happens here — exactly once, before any reporter runs — so counts stay correct regardless of how many reporters are attached.

f
warning(
config: Config,
message: string,
options?: NoticeOptions
): void

Something surprising that did not stop the run — a filter that matched nothing, a flag that does not apply to the chosen browser.

Interfaces

I
NoticeOptions

Options a diagnostic can carry beyond its text. Both default to the CLI's long-standing behaviour, so only the handful of call sites that need something else say so.

  • raw: boolean

    Write the message verbatim rather than as a # -prefixed TAP comment. For pre-formatted blocks — the coverage table, a stack trace — whose own layout is the point.

  • stream: "output" | "error" | "both"

    Which of the run's two streams the default rendering goes to. output by default.

Variables

v
BUILT_IN_REPORTERS: Record<ReporterName, new () => Reporter>

The --reporter value -> its class. Keyed by ReporterName, so adding a name to REPORTERS without wiring it up here is a type error rather than a silent fall back to tap.

lib/reporters/junit.ts

Classes

c
JUnitReporter

JUnit XML reporter — an additive artifact reporter, not a stdout format. Enabled with --junit[=<path>], it accumulates a <testcase> per testEnd and writes the document at run end, while whichever --reporter is active keeps owning stdout. That split matters: CI wants a readable log and a machine-readable file, and it's what --coverage=lcov already does for coverage artifacts.

Functions

f
buildXML(cases: JUnitCase[]): string

Builds the full JUnit XML document string from a flat list of test cases.

f
outputPath(context: ReporterContext): string

Resolves where the JUnit document is written: --junit=<path> (relative to the project root) when given a string, else <output>/junit.xml.

f
toCase(
context: ReporterContext,
details: TestDetails
): JUnitCase

Converts one testEnd into a JUnit <testcase>. Failing assertions are flattened into a failureDetail with stacks resolved back to original sources (same as the TAP at: field).

lib/reporters/spec.ts

Classes

c
SpecReporter

Human-readable reporter: tests nested under their QUnit module, with failures shown inline where they happen. The shape Node, Vitest and Mocha all default to — raw TAP is a poor local-dev experience.

Functions

f
formatFailureBlock(failures: FailureInfo[]): string

Renders every failing assertion of one test: message, values, and source location.

lib/reporters/tap.ts

Classes

c
TAPReporter

The default reporter: streams TAP version 13 to stdout. Stateless — every number it prints comes from context.counts, which the dispatcher updates before onTestEnd.

lib/reporters/types.ts

Functions

f
updateCounter(
counter: Counter,
details: TestDetails
): void

Applies one testEnd to the run's counters. Kept separate from any reporter so the numbers are identical no matter which reporter (or how many) is active — the exit code and the TAP plan both read counter, so it must be updated exactly once per test.

Interfaces

I
BrowserLog

A console.* call or an uncaught error from the page under test.

  • args: unknown[]

    The call's arguments, resolved to JSON values where the page could serialize them.

  • text: string

    The rendered single-line text. Always present.

  • type: string

    The page console type (log/warning/error/info/debug), or pageerror.

I
Notice

One diagnostic from qunitx itself: which files a narrowing flag scoped the run to, a filter that matched nothing, a build error, a timeout.

I
Reporter

The reporter contract — the public extension point for observing a run. Reporters render it to text (the built-in tap/spec/dot/github), write an artifact (junit), or simply collect, which is how the JS API turns a run into a value.

I
ReporterContext

What a reporter is given on every hook: where to write, what the run has counted so far, and the few resolved paths a message needs.

I
RunEndInfo

Final run info; the counts themselves live on config.state.results.counter.

I
RunStartInfo

Run-scope counts. fileCount === null means "counts unknown at this point" (watch mode, where the header is emitted per browser connection rather than per file batch).

I
TestAssertion

One QUnit assertion inside a testEnd payload.

I
TestDetails

The QUnit testEnd payload as it arrives over the WebSocket. Passing tests carry the trimmed { status, fullName, runtime }; failing tests additionally carry assertions.

Type Aliases

T
ReporterName = (REPORTERS)[number]

A valid --reporter value.

Variables

v
REPORTERS: "tap" | "spec" | "dot" | "github"[]

Every stdout reporter --reporter accepts, in help/error-message order. Exactly one is active per run — artifact outputs (--junit, --coverage) are separate additive flags. This module is a leaf (type-only imports), so Args.parse can validate against it without pulling the reporter implementations into the CLI's startup path.

lib/result/failure.ts

Classes

c
Failure<Code extends string = string, Data = unknown>(
code: Code,
message: string,
data: Data,
options?: FailureOptions
)

A structured, discriminable error.

Functions

f
attributes(failure: Any): TraceAttributes

Span/log attributes a failure declares for tracing: failure.code plus whatever its factory's trace mapper (see define) explicitly allowlisted from data. A failure whose factory declared no mapper yields the code alone — unmapped payload fields never leave the process, which is what makes redaction (passwords, tokens) the default rather than a discipline.

f
causes(error: unknown): unknown[]

Flattens an error's cause chain into an array, error first and the root cause last.

f
format(
error: unknown,
arg_1?: { stacks?: boolean; }
): string

Renders an error and its whole cause chain as indented, human-readable lines.

f
from(thrown: unknown): Any

Coerces any caught value into a Failure, leaving existing Failures untouched.

f
fromJSON(json: SerializedFailure): Any

Revives a SerializedFailure into a real Failure, reconstructing the cause chain.

f
hasCode<Codes extends readonly string[]>(
value: unknown,
...codes: Codes
): value is Failure<Codes[number], unknown>

Narrows a Failure to one of several codes — the multi-code sibling of Factory.is.

f
ignore(context: string): (error: unknown) => void

Builds a .catch() handler for a failure that genuinely has no consequence — but says so under QUNITX_DEBUG instead of vanishing. This is the raw handler; the ergonomic spelling at call sites is Task(promise).ignore(context), which wraps exactly this function.

f
is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
isFactory(value: unknown): value is FailureFactory<string, never>

True for a factory made by define. A factory carries the code it produces and its own is guard; a plain error-mapper carries neither, so an API can accept both in one position and tell them apart exactly, without guessing from arity.

f
isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
observed(failure: Any): void

Reports a declared failure to the observation seam — onObserved plus the OBSERVED_CHANNEL_NAME channel. Task's consuming methods call this at their classification points; call it yourself only for a synchronous Result flow that never crossed a Task, so a tracing adapter sees those failures too.

f
onIgnored(observer: IgnoredObserver | null): void

Installs a process-wide observer for every ignored failure — the interception seam for "list everything the program decided not to handle". Costs one null check on the failure path only (nothing on success, nothing per Task), and retains nothing itself: whether the suppressed failures accumulate, sample, or stream somewhere is the observer's decision. Pass null to detach.

f
onObserved(observer: ObservedObserver | null): void

Installs a process-wide observer for every handled declared failure — the portable (browser-friendly) counterpart of subscribing to OBSERVED_CHANNEL_NAME. Single slot, one null check on the failure path, nothing retained; pass null to detach.

f
rootCause(error: unknown): unknown

The deepest cause in the chain — the original failure, whatever wrapped it since.

f
setDebug(enabled: boolean): void

Toggles ignored-failure reporting at runtime, overriding the QUNITX_DEBUG default.

f
toJSON(error: unknown): SerializedFailure

Converts a Failure (or any error) into plain JSON.

Interfaces

I
DefineOptions

Per-kind options accepted by define().

  • trace: (data: Data) => TraceAttributes

    Allowlist mapper from the typed payload to span/log attributes, consumed by attributes. Deliberately not a redaction filter: only what the mapper returns is ever exposed, so sensitive fields are private by omission, not by scrubbing.

I
FailureFactory

A callable failure constructor produced by define(), carrying its own type guard.

I
FailureOptions

Options accepted by the Failure constructor and by every generated factory.

  • cause: unknown

    The error this failure was derived from. Preserved verbatim and walked by causes().

  • stackAnchor: (...args: never[]) => unknown

    The function to truncate the stack at, so the top frame is the code that reported the failure rather than the plumbing that built it. Defaults to the constructor.

  • stackless: boolean

    Skips stack capture. Only worth setting for failures produced in a hot loop and consumed immediately — the capture, not the allocation, is what a Failure costs. See the performance section of the docs before reaching for it.

I
SerializedFailure

The wire form of a Failure — what toJSON emits and fromJSON accepts.

  • cause:
    SerializedFailure
    | { name: string; message: string; stack?: string; }

    The serialized cause chain: a nested Failure, or a plain error's identifying fields.

  • code: string

    The discriminant. Survives the wire, unlike a prototype.

  • data: unknown

    The structured payload, JSON round-tripped by toJSON so it cannot fail later.

  • failure: true

    Wire marker. Symbol.for keys survive neither JSON.stringify nor structuredClone, so the serialized form carries an explicit field in the brand's place.

  • message: string

    The human-readable sentence, already interpolated from data.

  • stack: string

    The producing process's stack. Frameless — the header line alone — when the failure was built { stackless: true }; the field itself is absent only if stack was never set.

Type Aliases

T
Any = Failure<string, unknown>

Any Failure at all — the type to reach for when a signature accepts failures it does not enumerate, e.g. Result<T, Failure.Any> at a boundary that only logs.

T
IgnoredObserver = (
context: string,
error: unknown
) => void

The callback shape onIgnored installs: every ignored failure arrives with the label its call site declared.

T
ObservedObserver = (failure: Any) => void

The callback shape onObserved installs: every declared failure a consumer classified arrives right as it is handed to application code.

T
Of<F> = F extends FailureFactory<infer Code, infer Data> ? Failure<Code, Data> : never

The Failure type a factory produces: Failure.Of<typeof FileMissing>.

T
TraceAttributes = Record<string, string | number | boolean>

The attribute primitives tracing systems accept (OpenTelemetry's span-attribute values) — what a trace mapper returns and attributes yields.

Variables

v
IGNORED_CHANNEL_NAME: "qunitx.failure.ignored"

Name of the diagnostics_channel that ignore publishes to on Node and Deno — subscribe with the platform API, no qunitx registry involved. Messages are { context: string, error: unknown }. Browsers have no channel; use onIgnored.

v
OBSERVED_CHANNEL_NAME: "qunitx.failure.observed"

Name of the diagnostics_channel that observed publishes to on Node and Deno. Messages are { error: Failure.Any }. This is what a tracing adapter subscribes to — e.g. OpenTelemetry, in its entirety:

v
Unknown: FailureFactory<"Unknown", { thrown: unknown; }>

The failure from() produces for a throwable that is not already a Failure.

lib/result/index.ts

Classes

c
Failure.Failure<Code extends string = string, Data = unknown>(
code: Code,
message: string,
data: Data,
options?: FailureOptions
)

A structured, discriminable error.

Functions

f
all<O>(outcomes: ReadonlyArray<O>): Result<Exclude<O, Any>[], Extract<O, Any>>

Collects an array of outcomes into an array of values, short-circuiting on the first failure — the Result-shaped analogue of Promise.all.

f
expect<O>(
outcome: O,
message: string
): Exclude<O, Any>

Returns the success value, or throws new Error(message, { cause: failure }).

f
Failure.attributes(failure: Any): TraceAttributes

Span/log attributes a failure declares for tracing: failure.code plus whatever its factory's trace mapper (see define) explicitly allowlisted from data. A failure whose factory declared no mapper yields the code alone — unmapped payload fields never leave the process, which is what makes redaction (passwords, tokens) the default rather than a discipline.

f
Failure.causes(error: unknown): unknown[]

Flattens an error's cause chain into an array, error first and the root cause last.

f
Failure.format(
error: unknown,
arg_1?: { stacks?: boolean; }
): string

Renders an error and its whole cause chain as indented, human-readable lines.

f
Failure.from(thrown: unknown): Any

Coerces any caught value into a Failure, leaving existing Failures untouched.

f
Failure.fromJSON(json: SerializedFailure): Any

Revives a SerializedFailure into a real Failure, reconstructing the cause chain.

f
Failure.hasCode<Codes extends readonly string[]>(
value: unknown,
...codes: Codes
): value is Failure<Codes[number], unknown>

Narrows a Failure to one of several codes — the multi-code sibling of Factory.is.

f
Failure.ignore(context: string): (error: unknown) => void

Builds a .catch() handler for a failure that genuinely has no consequence — but says so under QUNITX_DEBUG instead of vanishing. This is the raw handler; the ergonomic spelling at call sites is Task(promise).ignore(context), which wraps exactly this function.

f
Failure.is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.isFactory(value: unknown): value is FailureFactory<string, never>

True for a factory made by define. A factory carries the code it produces and its own is guard; a plain error-mapper carries neither, so an API can accept both in one position and tell them apart exactly, without guessing from arity.

f
Failure.isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.observed(failure: Any): void

Reports a declared failure to the observation seam — onObserved plus the OBSERVED_CHANNEL_NAME channel. Task's consuming methods call this at their classification points; call it yourself only for a synchronous Result flow that never crossed a Task, so a tracing adapter sees those failures too.

f
Failure.onIgnored(observer: IgnoredObserver | null): void

Installs a process-wide observer for every ignored failure — the interception seam for "list everything the program decided not to handle". Costs one null check on the failure path only (nothing on success, nothing per Task), and retains nothing itself: whether the suppressed failures accumulate, sample, or stream somewhere is the observer's decision. Pass null to detach.

f
Failure.onObserved(observer: ObservedObserver | null): void

Installs a process-wide observer for every handled declared failure — the portable (browser-friendly) counterpart of subscribing to OBSERVED_CHANNEL_NAME. Single slot, one null check on the failure path, nothing retained; pass null to detach.

f
Failure.rootCause(error: unknown): unknown

The deepest cause in the chain — the original failure, whatever wrapped it since.

f
Failure.setDebug(enabled: boolean): void

Toggles ignored-failure reporting at runtime, overriding the QUNITX_DEBUG default.

f
Failure.toJSON(error: unknown): SerializedFailure

Converts a Failure (or any error) into plain JSON.

f
isErrno(
value: unknown,
...codes: string[]
): value is ErrnoError

Whether value is an Error carrying one of the given Node code strings — ENOENT, EADDRINUSE, EBUSY. With no codes it matches any error that has a string code at all (which includes Node's ERR_* internal errors, e.g. ERR_MODULE_NOT_FOUND).

f
partition<O>(outcomes: ReadonlyArray<O>): { values: Exclude<O, Any>[]; errors: Extract<O, Any>[]; }

Splits outcomes into their successes and their failures, keeping both.

f
rescue<T, F extends Any, A extends readonly unknown[]>(
fn: (...args: A) => T,
classify: (cause: unknown) => F,
...args: A
): Rescued<T, F>

Calls fn(...args) and returns the value bare, classifying any throw into the declared Failure classify builds — the boundary and the declaration fused into one expression, producing the bare union directly. The sync sibling of Task#mapErr: like that adapter edge it deliberately catches everything, because this IS the edge where a foreign throw becomes a declared failure — chain the original under cause. When the boundary should stay raw and the declaration should be a separate visible rethrow line, use Result.try instead.

f
tryCatch<T, A extends readonly unknown[]>(
fn: (...args: A) => T,
...args: A
): Tried<T>

Calls fn(...args) and reflects the outcome into a Caught box — Result.try, shaped like Promise.try. See the module doc for the flat-classification pattern this is half of.

f
tryCatch<T, A extends readonly unknown[]>(
fn: (...args: A) => T,
...args: A
): Tried<T>

Calls fn(...args) and reflects the outcome into a Caught box — Result.try, shaped like Promise.try. See the module doc for the flat-classification pattern this is half of.

f
unwrap<O>(outcome: O): Exclude<O, Any>

Returns the success value, or throws the failure.

f
unwrapOr<O, U>(
outcome: O,
fallback: U
): Exclude<O, Any> | U

Returns the success value, or fallback if the outcome is a failure.

Interfaces

I
ErrnoError

Minimal shape of a Node system error, declared locally so this module stays runtime-free.

I
Failure.DefineOptions

Per-kind options accepted by define().

  • trace: (data: Data) => TraceAttributes

    Allowlist mapper from the typed payload to span/log attributes, consumed by attributes. Deliberately not a redaction filter: only what the mapper returns is ever exposed, so sensitive fields are private by omission, not by scrubbing.

I
Failure.FailureFactory

A callable failure constructor produced by define(), carrying its own type guard.

I
Failure.FailureOptions

Options accepted by the Failure constructor and by every generated factory.

  • cause: unknown

    The error this failure was derived from. Preserved verbatim and walked by causes().

  • stackAnchor: (...args: never[]) => unknown

    The function to truncate the stack at, so the top frame is the code that reported the failure rather than the plumbing that built it. Defaults to the constructor.

  • stackless: boolean

    Skips stack capture. Only worth setting for failures produced in a hot loop and consumed immediately — the capture, not the allocation, is what a Failure costs. See the performance section of the docs before reaching for it.

I
Failure.SerializedFailure

The wire form of a Failure — what toJSON emits and fromJSON accepts.

  • cause:
    SerializedFailure
    | { name: string; message: string; stack?: string; }

    The serialized cause chain: a nested Failure, or a plain error's identifying fields.

  • code: string

    The discriminant. Survives the wire, unlike a prototype.

  • data: unknown

    The structured payload, JSON round-tripped by toJSON so it cannot fail later.

  • failure: true

    Wire marker. Symbol.for keys survive neither JSON.stringify nor structuredClone, so the serialized form carries an explicit field in the brand's place.

  • message: string

    The human-readable sentence, already interpolated from data.

  • stack: string

    The producing process's stack. Frameless — the header line alone — when the failure was built { stackless: true }; the field itself is absent only if stack was never set.

Namespaces

N
Failure

Structured, discriminable errors — the E half of Result<T, E>.

Type Aliases

T
Caught<T, E = unknown> = Ok<T> | Err<E>

What the boundary hands back: the value, or whatever was caught — unknown, honestly, because a catch binding is exactly as untrustworthy.

T
Failure.Any = Failure<string, unknown>

Any Failure at all — the type to reach for when a signature accepts failures it does not enumerate, e.g. Result<T, Failure.Any> at a boundary that only logs.

T
Failure.IgnoredObserver = (
context: string,
error: unknown
) => void

The callback shape onIgnored installs: every ignored failure arrives with the label its call site declared.

T
Failure.ObservedObserver = (failure: Any) => void

The callback shape onObserved installs: every declared failure a consumer classified arrives right as it is handed to application code.

T
Failure.Of<F> = F extends FailureFactory<infer Code, infer Data> ? Failure<Code, Data> : never

The Failure type a factory produces: Failure.Of<typeof FileMissing>.

T
Failure.TraceAttributes = Record<string, string | number | boolean>

The attribute primitives tracing systems accept (OpenTelemetry's span-attribute values) — what a trace mapper returns and attributes yields.

T
Ok<T> = { readonly ok: true; readonly value: T; readonly error?: undefined; }

The success variant of a caught outcome. error is present-but-undefined for shape stability.

T
Rescued<T, F> = 0 extends 1 & T ? unknown : [T] extends [PromiseLike<unknown>] ? Promise<Awaited<T> | F> : T | F

The outcome of rescue(): the bare T | F union, promise-wrapped when fn was async. A leaking any collapses to unknown so it cannot pose as an inspected type.

T
Result<T, E = Any> = T | E

The success value or a declared failure — a bare union, discriminated by the Failure brand.

Variables

v
Failure.IGNORED_CHANNEL_NAME: "qunitx.failure.ignored"

Name of the diagnostics_channel that ignore publishes to on Node and Deno — subscribe with the platform API, no qunitx registry involved. Messages are { context: string, error: unknown }. Browsers have no channel; use onIgnored.

v
Failure.OBSERVED_CHANNEL_NAME: "qunitx.failure.observed"

Name of the diagnostics_channel that observed publishes to on Node and Deno. Messages are { error: Failure.Any }. This is what a tracing adapter subscribes to — e.g. OpenTelemetry, in its entirety:

v
Failure.Unknown: FailureFactory<"Unknown", { thrown: unknown; }>

The failure from() produces for a throwable that is not already a Failure.

lib/result/result.ts

Functions

f
all<O>(outcomes: ReadonlyArray<O>): Result<Exclude<O, Any>[], Extract<O, Any>>

Collects an array of outcomes into an array of values, short-circuiting on the first failure — the Result-shaped analogue of Promise.all.

f
expect<O>(
outcome: O,
message: string
): Exclude<O, Any>

Returns the success value, or throws new Error(message, { cause: failure }).

f
partition<O>(outcomes: ReadonlyArray<O>): { values: Exclude<O, Any>[]; errors: Extract<O, Any>[]; }

Splits outcomes into their successes and their failures, keeping both.

f
unwrap<O>(outcome: O): Exclude<O, Any>

Returns the success value, or throws the failure.

f
unwrapOr<O, U>(
outcome: O,
fallback: U
): Exclude<O, Any> | U

Returns the success value, or fallback if the outcome is a failure.

Type Aliases

T
Result<T, E = Any> = T | E

The success value or a declared failure — a bare union, discriminated by the Failure brand.

lib/result/try.ts

Functions

f
isErrno(
value: unknown,
...codes: string[]
): value is ErrnoError

Whether value is an Error carrying one of the given Node code strings — ENOENT, EADDRINUSE, EBUSY. With no codes it matches any error that has a string code at all (which includes Node's ERR_* internal errors, e.g. ERR_MODULE_NOT_FOUND).

f
rescue<T, F extends Any, A extends readonly unknown[]>(
fn: (...args: A) => T,
classify: (cause: unknown) => F,
...args: A
): Rescued<T, F>

Calls fn(...args) and returns the value bare, classifying any throw into the declared Failure classify builds — the boundary and the declaration fused into one expression, producing the bare union directly. The sync sibling of Task#mapErr: like that adapter edge it deliberately catches everything, because this IS the edge where a foreign throw becomes a declared failure — chain the original under cause. When the boundary should stay raw and the declaration should be a separate visible rethrow line, use Result.try instead.

f
tryCatch<T, A extends readonly unknown[]>(
fn: (...args: A) => T,
...args: A
): Tried<T>

Calls fn(...args) and reflects the outcome into a Caught box — Result.try, shaped like Promise.try. See the module doc for the flat-classification pattern this is half of.

Interfaces

I
ErrnoError

Minimal shape of a Node system error, declared locally so this module stays runtime-free.

Type Aliases

T
Caught<T, E = unknown> = Ok<T> | Err<E>

What the boundary hands back: the value, or whatever was caught — unknown, honestly, because a catch binding is exactly as untrustworthy.

T
Ok<T> = { readonly ok: true; readonly value: T; readonly error?: undefined; }

The success variant of a caught outcome. error is present-but-undefined for shape stability.

T
Rescued<T, F> = 0 extends 1 & T ? unknown : [T] extends [PromiseLike<unknown>] ? Promise<Awaited<T> | F> : T | F

The outcome of rescue(): the bare T | F union, promise-wrapped when fn was async. A leaking any collapses to unknown so it cannot pose as an inspected type.

lib/selection/filter.ts

Functions

f
buildQUnitFilterQuery(config: FilterConfig): string

Builds the ?filter=… query that carries the test filter (-t/--filter/-m/--module) into the page.

f
describeActiveFilters(config: FilterConfig): string

Human-readable description of the active filters, for the "nothing matched" message.

f
isFilteredRun(config: FilterConfig): boolean

True when this run selects a subset of the tests inside the files it loads.

lib/selection/line-targets.ts

Functions

f
selectorsFromScan(
scan: DeclarationScan,
lines: number[],
displayPath: string
): LineTargetResolution

Resolves line targets against an ALREADY-parsed scan. Split out so --search, which has scanned every file for its listing, can resolve line targets without reading and esbuild-transforming those files a second time.

Interfaces

I
LineTargetResolution

Result of resolving a file's line targets into selectors, plus any diagnostics to surface.

I
QUnitSelector

One thing a file#34 target selects. test omitted means "this module and everything nested under it" — used for module targets, and as the fallback when a test's name is computed and so cannot be matched exactly.

  • module: string

    Full module path, ' > '-joined. '' for a top-level test.

  • test: string

    Exact test name; omitted to select the whole module and its nested children.

lib/selection/parse-test-declarations.ts

Functions

f
parseTestDeclarations(
source: string,
filePath: string
): Promise<DeclarationScan | null>

Finds every test(...) / module(...) declaration in a test file, with the source line range each one spans — enough to answer "which test is at line 34?".

Interfaces

I
DeclarationScan

Every declaration found in a file, plus whether it uses only().

I
TestDeclaration

A test(...) or module(...) call found in a test file, in 1-based source lines.

lib/selection/qunit-matcher.ts

Functions

f
buildQUnitFullName(
modulePath: string,
testName: string
): string

The string a filter is matched against: "Module: test name", with nested modules already joined by " > ". A top-level test has an empty module name, giving ": test name" — which is QUnit's own behaviour, not a quirk of this port.

f
matchQUnitFilter(
filter: string | undefined,
fullName: string
): boolean

True when filter selects fullName, using QUnit's semantics:

lib/setup/browser.ts

Functions

f
launch(
config: LaunchTarget,
skipPrelaunch?: boolean,
headed?: boolean
): Promise<Browser>

Launches a browser for the given config.browser type. For chromium: connects via CDP to the pre-launched Chrome (fast path) or falls back to chromium.launch() if pre-launch failed. For firefox/webkit: uses playwright's standard launch (requires npx playwright install [browser]).

f
setup(
config: Config,
existingBrowser?: Browser | null,
sharedServer?: HTTPServer | null
): Promise<Connections>

Launches a Playwright browser (or reuses an existing one), starts the web server, and returns the page/server/browser connection object.

f
showsWithoutDebug(
type: string,
text: string
): boolean

Whether a page console message prints without --debug. Warnings and errors do, so CI logs capture browser-side failures; other levels are debug-only. Firefox also reports its own parser diagnostics as warnings — [JavaScript Warning: "unreachable code after return statement" …], about the bundle rather than anything the page said, and on every run — so those are debug-only.

Interfaces

I
LaunchTarget

The three fields launch actually reads. Narrower than Config on purpose: qunitx run has a config of its own with no test files in it, and asking it to fake thirty unrelated fields to borrow the CDP pre-launch fast path would be a cast pretending to be a type.

lib/setup/config.ts

Functions

f
setup(options?: ConfigOptions): Task<Config, ConfigFailure>

Builds the merged qunitx config from package.json settings and either an argv or an explicit ConfigOptions. package.json#qunitx.plugins entries are dynamic-imported into esbuild plugin objects.

Interfaces

I
ConfigOptions

Resolved settings handed to setup in place of an argv, by the JS API and by anyone else assembling a run programmatically. Every CLI flag has a field here — they are the same settings, arrived at by a different route — plus the two things argv cannot express: a working directory, and esbuild plugins as live objects rather than module specifiers.

  • console: Console

    Where reporter text and # diagnostics go. Defaults to the process streams; pass silentConsole for a run that prints nothing at all. Named apart from output — the build directory — because they are unrelated and both spellings are load-bearing.

  • cwd: string

    Directory the project root and relative inputs resolve against. Defaults to process.cwd().

  • esbuildPlugins: EsbuildPlugin[]

    Live esbuild plugin objects, appended after any resolved from package.json#qunitx.plugins.

  • reporters: ReporterInstance[]

    Reporter instances for this run, replacing the one reporter/junit would have selected. The way a caller observes a run as data rather than as text.

  • signal: AbortSignal

    Cancels the run when it fires. Placed on state.signal; setup never subscribes to it.

Type Aliases

Variables

lib/setup/file-watcher.ts

Functions

f
handleWatchEvent(
config: Config,
extensions: string[],
event: string,
filePath: string,
onEventFunc: (
event: string,
file: string
) => unknown
,
onFinishFunc: ((
path: string,
event: string
) => void) | null | undefined
): Promise<void>

Routes a file-system event to fsTree mutation and optional rebuild trigger. unlinkDir bypasses the extension filter so deleted directories always clean up fsTree. When a build is already in progress, queues the event as a pending trigger (last-write-wins).

f
mutateFSTree(
fsTree: FSTree,
event: string,
filePath: string
): void

Mutates fsTree in place based on a file-system event.

f
readFileStable(
filePath: string,
read?: (p: string) => Promise<Buffer>
): Promise<Buffer>

Reads a file, re-reading until two reads spaced STABILITY_GAP_MS apart return byte-identical content — so a file caught mid-write (Windows truncate→flush; see STABILITY_GAP_MS) is hashed only once its bytes have settled. Bounded by MAX_STABILITY_ATTEMPTS; returns the latest content if it never stabilizes. read is injectable for tests; production reads from disk. Read errors propagate so the caller's catch can treat a vanished file as a removal.

f
rescanDirectoryForDelta(
watchPath: string,
config: Config,
extensions: string[],
onEventFunc: (
event: string,
file: string
) => unknown
,
onFinishFunc: ((
path: string,
event: string
) => void) | null | undefined
,
trackSymlinkFn?: (filePath: string) => void,
deps?: RescanDeps
): Promise<void>

Scans watchPath recursively and fires add / change / unlink events for any delta between the directory contents and config.fsTree. Used as a 1 s safety-net poll on macOS where FSEvents can drop events under load — additions and removals are recovered from the directory listing, and modifications are recovered by re-stat'ing every tracked file and firing change whenever its mtime is newer than config.state.watch.lastBuildEndMs (the moment the last build saw the file). The seed for that baseline is set in setup.

f
setup(
testFileLookupPaths: string[],
config: Config,
onEventFunc: (
event: string,
file: string
) => unknown
,
onFinishFunc: ((
path: string,
event: string
) => void) | null | undefined
): { fileWatchers: Record<string, FSWatcher>; killFileWatchers: () => Record<string, FSWatcher>; ready: Promise<void>; }

Starts fs.watch watchers for each lookup path and calls onEventFunc on JS/TS file changes, debounced via a per-file timestamp. Also watches each path's parent directory to detect when a watched directory is renamed or deleted (since fs.watch tracks by inode, not path). Uses config.fsTree to distinguish unlink (tracked file) from unlinkDir (directory) on deletion.

f
toWatchableRoot(lookupPath: string): string

Maps a lookup path to a path fs.watch can actually watch. A real file or directory is returned as-is; a glob is walked up to the deepest ancestor directory that exists — its base dir — which fs.watch can watch recursively (test/x/!(plugin).ts collapses to test/x).

Interfaces

I
RescanDeps

The fs calls rescanDirectoryForDelta makes — injectable so a platform-specific readdir/stat disagreement can be reproduced deterministically. Production uses the real fs.

  • readdir: readdir

    Lists the directory tree. Its dirent types are unreliable on Windows — see isMissing.

  • stat: stat

    Resolves a path (follows symlinks). Used to drop entries that are genuinely gone.

lib/setup/fs-tree.ts

Functions

f
build(
fileAbsolutePaths: string[],
config?: { extensions?: string[]; }
): Task<FSTree, InputUnreadableFailure>

Resolves an array of file paths, directories, or glob patterns into a flat { absolutePath: null } map.

Type Aliases

Variables

v
InputUnreadable: Failure.FailureFactory<"InputUnreadable", { input: string; }>

One of the configured test inputs could not be globbed, stat'd or walked.

lib/setup/get-changed-fs-tree.ts

Functions

f
getChangedFsTree(
fsTree: FSTree,
config: Config,
changedSince: string,
getChanged?: getChangedFilePathsInGitSince
): Promise<FSTree>

Returns a new fsTree containing only the test files affected by changes since ref, per the cached esbuild metafile's reverse-dependency graph.

lib/setup/keyboard-events.ts

Functions

f
setup(session: WatchSession): void

Binds watch-mode keyboard shortcuts to the session's verbs: qq aborts, qa runs all, qf re-runs the last failures, ql repeats the last run.

lib/setup/qunit-harness.ts

Functions

f
harness(options: { timeout: number; }): void

Installs globalThis.__qunitxHarness: run the tests this page has registered, on command, and be ready for more.

lib/setup/qunitx-runtime-plugin.ts

Functions

f
qunitxRuntimePlugin(cwd?: string): Plugin

esbuild plugin that lets test files import { module, test } from 'qunitx' even when the consumer project has NOT installed the separate qunitx runtime package — the case for JSR/standalone-binary/npx users, who have no node_modules at all.

lib/setup/remote-inputs.ts

Functions

f
isRemoteGlob(input: string): boolean

Whether an input carries glob metacharacters — the same test the local walk makes.

f
isRemoteInput(input: string): boolean

Whether an input names something on a server rather than on this disk.

f
namedInWords(message: string): string | null

The same conditions as REFUSALS, recognised from a runtime that reports them only in prose.

f
remoteDirectoryPattern(url: string): string | null

The recursive pattern a remote DIRECTORY input means, or null where the URL names a file.

Type Aliases

T
RemoteUnreadableFailure = Failure.Of<RemoteUnreadable>

The one failure this module declares.

Variables

v
RemoteUnreadable: Failure.FailureFactory<"RemoteUnreadable", { url: string; why: string; }>

A remote input could not be fetched — refused, 404, or not something a module can be made of.

lib/setup/remote-module-plugin.ts

Functions

f
loaderFor(url: string): Loader

Which loader a URL's extension asks for.

f
remoteModulePlugin(cwd?: string): Plugin

esbuild plugin that lets a bundle reach modules served over HTTP, so qunitx https://example.com/tests/cart-test.js runs the same way a local path does.

Variables

v
REMOTE_NAMESPACE: "qunitx-remote"

The esbuild namespace a fetched module lives in. Exported so a plugin that RESOLVES an entry itself — qunitx run's, which hands esbuild a path rather than a specifier — can mark a URL as belonging here; a plain path would otherwise be looked for on this disk.

lib/setup/remote-page.ts

Functions

f
addQUnitFilter(
url: string,
filter: string | undefined
): string

The URL to open, with the run's own filter applied as QUnit's.

f
couldBeAPage(url: string): boolean

Whether a URL could name a page at all: .html, or no extension.

f
filterRemotePagesFromConfig(config: Config): Promise<string[]>

The inputs of a run that are pages somebody else serves, in the order they were given.

f
isRemoteQUnitPage(url: string): Promise<boolean>

Whether a URL answers with a QUnit page, remembered per process.

f
looksLikeQUnitPage(html: string): boolean

What a QUnit page looks like from the outside.

f
runQUnitRemotePage(
config: Config,
page: Page,
url: string
): Promise<void>

Opens one remote QUnit page and reports what it does, test by test.

f
trapQUnitHooks(): void

The page-side hook, as one self-contained function.

Type Aliases

T
PageUnrunnableFailure = Failure.Of<PageUnrunnable>

The one failure this module declares.

Variables

v
PageUnrunnable: Failure.FailureFactory<"PageUnrunnable", { url: string; why: string; }>

The page could not be driven — it never loaded, or QUnit never finished.

lib/setup/run-state.ts

Functions

f
clearBundles(build: BuildState): void

Invalidates a group's compiled bundles so the next run rebuilds them from disk.

f
create(): RunState

Fresh run state for a single qunitx invocation. Built once per run in Config.setup().

f
newGroup(
index?: number,
selectors?: QUnitSelector[]
): GroupState

Fresh per-group state. One per concurrent group; the group spread in runConcurrentMode replaces state.group with this so groups never share the slots inside it.

f
requestAbort(state: RunState): void

Asks every live server to tell its pages to drop the rest of the QUnit queue, and records that this run was cut short.

f
reset(
results: RunResults,
coverageEnabled: boolean
): void

Clears the run accumulators in place for a re-run.

f
reusablePageSlot(state: RunState): { page: Page | null; } | null

The daemon's reusable Page slot, or null when reuse does not apply.

lib/setup/test-file-paths.ts

Functions

f
setup(inputs: string[]): string[]

Deduplicates a list of file, folder, and glob inputs so that more-specific paths covered by broader ones are removed.

lib/setup/web-server.ts

Functions

f
buildErrorHTML(buildError: { type: string; formatted: string; }): string

Generates a self-contained HTML error page for a build failure, styled to match the QUnit HTML reporter (same element IDs, colors, and layout as qunit.css / qunitjs.com). Includes a WebSocket reconnect script that reloads the page on 'refresh' (next successful build). Uses location.port so no port needs to be baked in at generation time; the script is a no-op when the page is opened as a static file (location.port is empty).

f
buildNoTestsHTML(files: string[]): string

Generates a self-contained HTML warning page shown when a test run completes with 0 registered QUnit tests. Styled to match the QUnit HTML reporter, with an amber banner instead of red. Includes the same WebSocket reconnect script as buildErrorHTML so the page reloads on the next successful build.

f
registerGroupRoutes(
server: HTTPServer,
groupConfig: Config
): void

Registers HTML and JS bundle routes for one concurrent group on a shared HTTPServer. Routes: GET /group-${groupId}/ and GET /group-${groupId}/tests.js.

f
registerSharedStaticHandler(
server: HTTPServer,
groupConfigs: Config[]
): void

Registers a GET /* wildcard handler on a shared HTTPServer that serves static assets from each group's output directory, routing by /group-{id}/ URL prefix.

f
setup(config: Config): HTTPServer

Creates and returns an HTTPServer with routes for the test HTML, filtered test page, and static assets, plus a WebSocket handler that streams TAP events.

f
setupGroupWSHandler(
server: HTTPServer,
groupConfigs: Config[]
): void

Attaches the shared WebSocket event dispatcher to server.wss. Routes each socket's messages to the correct group's Config using the groupId baked into the browser-side wsOpen message by testRuntimeToInject.

f
testRuntimeSource(
config: Config,
groupId?: number
): string

The same runtime as bare JavaScript, for a page that injects it rather than serving it.

f
testRuntimeToInject(
config: Config,
groupId?: number
): string

The <script> that turns a page into a reporting test page: the QUnit preconfig, the WebSocket client, and the QUnit hooks that forward every event to this process.

Variables

lib/setup/write-output-static-files.ts

Functions

f
writeOutputStaticFiles(
arg_0: { projectRoot: string; output: string; },
htmlAssets: HtmlAssets
): Promise<void>

Copies static HTML files and referenced assets from the project into the configured output directory.

lib/stream/index.ts

Classes

c
Failure.Failure<Code extends string = string, Data = unknown>(
code: Code,
message: string,
data: Data,
options?: FailureOptions
)

A structured, discriminable error.

c
T
Stream<T, E = never>(open: () => AsyncGenerator<T | E>)

The exported value: builders (Stream.from, Stream.unfold, Stream.lines) are the only entry points — there is no public constructor and no call form.

Functions

f
Failure.attributes(failure: Any): TraceAttributes

Span/log attributes a failure declares for tracing: failure.code plus whatever its factory's trace mapper (see define) explicitly allowlisted from data. A failure whose factory declared no mapper yields the code alone — unmapped payload fields never leave the process, which is what makes redaction (passwords, tokens) the default rather than a discipline.

f
Failure.causes(error: unknown): unknown[]

Flattens an error's cause chain into an array, error first and the root cause last.

f
Failure.format(
error: unknown,
arg_1?: { stacks?: boolean; }
): string

Renders an error and its whole cause chain as indented, human-readable lines.

f
Failure.from(thrown: unknown): Any

Coerces any caught value into a Failure, leaving existing Failures untouched.

f
Failure.fromJSON(json: SerializedFailure): Any

Revives a SerializedFailure into a real Failure, reconstructing the cause chain.

f
Failure.hasCode<Codes extends readonly string[]>(
value: unknown,
...codes: Codes
): value is Failure<Codes[number], unknown>

Narrows a Failure to one of several codes — the multi-code sibling of Factory.is.

f
Failure.ignore(context: string): (error: unknown) => void

Builds a .catch() handler for a failure that genuinely has no consequence — but says so under QUNITX_DEBUG instead of vanishing. This is the raw handler; the ergonomic spelling at call sites is Task(promise).ignore(context), which wraps exactly this function.

f
Failure.is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.isFactory(value: unknown): value is FailureFactory<string, never>

True for a factory made by define. A factory carries the code it produces and its own is guard; a plain error-mapper carries neither, so an API can accept both in one position and tell them apart exactly, without guessing from arity.

f
Failure.isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.observed(failure: Any): void

Reports a declared failure to the observation seam — onObserved plus the OBSERVED_CHANNEL_NAME channel. Task's consuming methods call this at their classification points; call it yourself only for a synchronous Result flow that never crossed a Task, so a tracing adapter sees those failures too.

f
Failure.onIgnored(observer: IgnoredObserver | null): void

Installs a process-wide observer for every ignored failure — the interception seam for "list everything the program decided not to handle". Costs one null check on the failure path only (nothing on success, nothing per Task), and retains nothing itself: whether the suppressed failures accumulate, sample, or stream somewhere is the observer's decision. Pass null to detach.

f
Failure.onObserved(observer: ObservedObserver | null): void

Installs a process-wide observer for every handled declared failure — the portable (browser-friendly) counterpart of subscribing to OBSERVED_CHANNEL_NAME. Single slot, one null check on the failure path, nothing retained; pass null to detach.

f
Failure.rootCause(error: unknown): unknown

The deepest cause in the chain — the original failure, whatever wrapped it since.

f
Failure.setDebug(enabled: boolean): void

Toggles ignored-failure reporting at runtime, overriding the QUNITX_DEBUG default.

f
Failure.toJSON(error: unknown): SerializedFailure

Converts a Failure (or any error) into plain JSON.

Interfaces

I
Channel

The producer half of StreamClass.channel: emit into it, consume stream out of it.

  • abort(reason: unknown): void

    Rejects the consuming Task with reason — the two-tier rule's bug tier. Idempotent.

  • buffered: number

    Elements buffered for a consumer that has not taken them yet.

  • close(): void

    Ends the stream once the buffer drains. Idempotent.

  • closed: boolean

    Whether the channel has been closed, aborted, or abandoned by its consumer.

  • dropped: number

    How many elements the buffer has lost to overflow. 0 unless a consumer fell behind.

  • emit(value: T): boolean

    Offers a value. Returns false when there is no room left — Node's write() convention: advisory for a producer that can slow down, ignorable for one that cannot.

  • fail(error: E): boolean

    Offers a declared failure as an element — the railway, not a rejection.

  • ready(): Promise<void>

    Resolves once there is room to emit again — Web Streams' writer.ready, and the promise form of Node's 'drain'.

  • stream: Stream<T, E>

    The consuming half. One consumer only; a second pass throws.

I
ChannelOptions

How a Channel behaves when its consumer cannot keep up.

  • capacity: number

    How many elements to buffer for a consumer that has not taken them yet. Default 10_000.

  • onDemand: () => void

    Called once, when a consumer first attaches. A producer that can defer starting should start here: it is the difference between a buffer that stays near empty and one that races ahead of a consumer that has not arrived.

  • onDiscard: (
    dropped: T | E,
    buffered: number
    ) => void

    Called with each element the buffer actually lost, and the depth after the loss. The only place overflow is observable — make it fatal from here by calling fail or abort.

  • overflow: Overflow

    What to do once capacity is reached: drop from either end, or 'fail' — end the stream with a ChannelOverflowFailure element instead of losing anything quietly. Default 'dropOldest'.

I
Failure.DefineOptions

Per-kind options accepted by define().

  • trace: (data: Data) => TraceAttributes

    Allowlist mapper from the typed payload to span/log attributes, consumed by attributes. Deliberately not a redaction filter: only what the mapper returns is ever exposed, so sensitive fields are private by omission, not by scrubbing.

I
Failure.FailureFactory

A callable failure constructor produced by define(), carrying its own type guard.

I
Failure.FailureOptions

Options accepted by the Failure constructor and by every generated factory.

  • cause: unknown

    The error this failure was derived from. Preserved verbatim and walked by causes().

  • stackAnchor: (...args: never[]) => unknown

    The function to truncate the stack at, so the top frame is the code that reported the failure rather than the plumbing that built it. Defaults to the constructor.

  • stackless: boolean

    Skips stack capture. Only worth setting for failures produced in a hot loop and consumed immediately — the capture, not the allocation, is what a Failure costs. See the performance section of the docs before reaching for it.

I
Failure.SerializedFailure

The wire form of a Failure — what toJSON emits and fromJSON accepts.

  • cause:
    SerializedFailure
    | { name: string; message: string; stack?: string; }

    The serialized cause chain: a nested Failure, or a plain error's identifying fields.

  • code: string

    The discriminant. Survives the wire, unlike a prototype.

  • data: unknown

    The structured payload, JSON round-tripped by toJSON so it cannot fail later.

  • failure: true

    Wire marker. Symbol.for keys survive neither JSON.stringify nor structuredClone, so the serialized form carries an explicit field in the brand's place.

  • message: string

    The human-readable sentence, already interpolated from data.

  • stack: string

    The producing process's stack. Frameless — the header line alone — when the failure was built { stackless: true }; the field itself is absent only if stack was never set.

Namespaces

N
Failure

Structured, discriminable failures — the E elements of a Stream.

Type Aliases

T
ChannelOverflowFailure = Of<ChannelOverflow>

The failure element a 'fail' channel ends with.

T
Failure.Any = Failure<string, unknown>

Any Failure at all — the type to reach for when a signature accepts failures it does not enumerate, e.g. Result<T, Failure.Any> at a boundary that only logs.

T
Failure.IgnoredObserver = (
context: string,
error: unknown
) => void

The callback shape onIgnored installs: every ignored failure arrives with the label its call site declared.

T
Failure.ObservedObserver = (failure: Any) => void

The callback shape onObserved installs: every declared failure a consumer classified arrives right as it is handed to application code.

T
Failure.Of<F> = F extends FailureFactory<infer Code, infer Data> ? Failure<Code, Data> : never

The Failure type a factory produces: Failure.Of<typeof FileMissing>.

T
Failure.TraceAttributes = Record<string, string | number | boolean>

The attribute primitives tracing systems accept (OpenTelemetry's span-attribute values) — what a trace mapper returns and attributes yields.

T
Overflow = "dropOldest" | "dropNewest" | "fail"

What a full channel does — the two GenStage :buffer_keep choices, named for the element that goes rather than the one that stays, plus the option of refusing to lose anything silently.

T
Source<T> = AsyncIterable<T> | Iterable<T>

Anything a Stream can be built from or flattened into: sync or async iterables (a web ReadableStream is async-iterable on every modern runtime).

Variables

v
ChannelOverflow: FailureFactory<"ChannelOverflow", { capacity: number; }>

A channel with overflow: 'fail' filled up: the consumer fell far enough behind that the buffer could not hold the difference, and dropping was not on the table.

v
Failure.IGNORED_CHANNEL_NAME: "qunitx.failure.ignored"

Name of the diagnostics_channel that ignore publishes to on Node and Deno — subscribe with the platform API, no qunitx registry involved. Messages are { context: string, error: unknown }. Browsers have no channel; use onIgnored.

v
Failure.OBSERVED_CHANNEL_NAME: "qunitx.failure.observed"

Name of the diagnostics_channel that observed publishes to on Node and Deno. Messages are { error: Failure.Any }. This is what a tracing adapter subscribes to — e.g. OpenTelemetry, in its entirety:

v
Failure.Unknown: FailureFactory<"Unknown", { thrown: unknown; }>

The failure from() produces for a throwable that is not already a Failure.

v
T
Task: TaskConstructor

Call-or-construct, like Boolean/Date: Task(recipe) and new Task(recipe) build the same lazy Task. ES classes reject the call form, so the export is a Proxy whose apply forwards to construction — statics, instanceof, and the prototype all pass through.

lib/stream/stream.ts

Classes

c
T
Stream<T, E = never>(open: () => AsyncGenerator<T | E>)

The exported value: builders (Stream.from, Stream.unfold, Stream.lines) are the only entry points — there is no public constructor and no call form.

Interfaces

I
Channel

The producer half of StreamClass.channel: emit into it, consume stream out of it.

  • abort(reason: unknown): void

    Rejects the consuming Task with reason — the two-tier rule's bug tier. Idempotent.

  • buffered: number

    Elements buffered for a consumer that has not taken them yet.

  • close(): void

    Ends the stream once the buffer drains. Idempotent.

  • closed: boolean

    Whether the channel has been closed, aborted, or abandoned by its consumer.

  • dropped: number

    How many elements the buffer has lost to overflow. 0 unless a consumer fell behind.

  • emit(value: T): boolean

    Offers a value. Returns false when there is no room left — Node's write() convention: advisory for a producer that can slow down, ignorable for one that cannot.

  • fail(error: E): boolean

    Offers a declared failure as an element — the railway, not a rejection.

  • ready(): Promise<void>

    Resolves once there is room to emit again — Web Streams' writer.ready, and the promise form of Node's 'drain'.

  • stream: Stream<T, E>

    The consuming half. One consumer only; a second pass throws.

I
ChannelOptions

How a Channel behaves when its consumer cannot keep up.

  • capacity: number

    How many elements to buffer for a consumer that has not taken them yet. Default 10_000.

  • onDemand: () => void

    Called once, when a consumer first attaches. A producer that can defer starting should start here: it is the difference between a buffer that stays near empty and one that races ahead of a consumer that has not arrived.

  • onDiscard: (
    dropped: T | E,
    buffered: number
    ) => void

    Called with each element the buffer actually lost, and the depth after the loss. The only place overflow is observable — make it fatal from here by calling fail or abort.

  • overflow: Overflow

    What to do once capacity is reached: drop from either end, or 'fail' — end the stream with a ChannelOverflowFailure element instead of losing anything quietly. Default 'dropOldest'.

Type Aliases

T
ChannelOverflowFailure = Of<ChannelOverflow>

The failure element a 'fail' channel ends with.

T
Overflow = "dropOldest" | "dropNewest" | "fail"

What a full channel does — the two GenStage :buffer_keep choices, named for the element that goes rather than the one that stays, plus the option of refusing to lose anything silently.

T
Source<T> = AsyncIterable<T> | Iterable<T>

Anything a Stream can be built from or flattened into: sync or async iterables (a web ReadableStream is async-iterable on every modern runtime).

Variables

v
ChannelOverflow: FailureFactory<"ChannelOverflow", { capacity: number; }>

A channel with overflow: 'fail' filled up: the consumer fell far enough behind that the buffer could not hold the difference, and dropping was not on the table.

lib/tap/display-final-result.ts

Functions

f
displayFinalResult(
arg_0: Counter,
timeTaken: number,
output?: Console
): void

Prints the TAP plan line and test-run summary (total, pass, skip, fail, duration).

lib/tap/display-test-result.ts

Functions

f
displayTestResult(
testNumber: number,
details: TestDetails,
failures?: FailureInfo[],
output?: Console
): void

Formats and prints a single QUnit testEnd event as a TAP ok/not ok line with an optional YAML failure block. A pure formatter: testNumber is the TAP sequence number (the caller owns counting) and failures are pre-resolved by failedAssertions.

lib/tap/dump-yaml.ts

Functions

f
dumpYaml(arg_0: { name: string; actual: unknown; expected: unknown; message: string | null; stack: string | null; source: string | null; at: string | null; }): string

Serializes the fixed TAP assertion object to a YAML string. Uses a template literal (no Object.entries overhead) for the known top-level keys.

lib/tap/index.ts

Functions

f
displayFinalResult(
arg_0: Counter,
timeTaken: number,
output?: Console
): void

Prints the TAP plan line and test-run summary (total, pass, skip, fail, duration).

f
displayTestResult(
testNumber: number,
details: TestDetails,
failures?: FailureInfo[],
output?: Console
): void

Formats and prints a single QUnit testEnd event as a TAP ok/not ok line with an optional YAML failure block. A pure formatter: testNumber is the TAP sequence number (the caller owns counting) and failures are pre-resolved by failedAssertions.

f
dumpYaml(arg_0: { name: string; actual: unknown; expected: unknown; message: string | null; stack: string | null; source: string | null; at: string | null; }): string

Serializes the fixed TAP assertion object to a YAML string. Uses a template literal (no Object.entries overhead) for the known top-level keys.

lib/task/index.ts

Classes

c
Failure.Failure<Code extends string = string, Data = unknown>(
code: Code,
message: string,
data: Data,
options?: FailureOptions
)

A structured, discriminable error.

Functions

f
Failure.attributes(failure: Any): TraceAttributes

Span/log attributes a failure declares for tracing: failure.code plus whatever its factory's trace mapper (see define) explicitly allowlisted from data. A failure whose factory declared no mapper yields the code alone — unmapped payload fields never leave the process, which is what makes redaction (passwords, tokens) the default rather than a discipline.

f
Failure.causes(error: unknown): unknown[]

Flattens an error's cause chain into an array, error first and the root cause last.

f
Failure.format(
error: unknown,
arg_1?: { stacks?: boolean; }
): string

Renders an error and its whole cause chain as indented, human-readable lines.

f
Failure.from(thrown: unknown): Any

Coerces any caught value into a Failure, leaving existing Failures untouched.

f
Failure.fromJSON(json: SerializedFailure): Any

Revives a SerializedFailure into a real Failure, reconstructing the cause chain.

f
Failure.hasCode<Codes extends readonly string[]>(
value: unknown,
...codes: Codes
): value is Failure<Codes[number], unknown>

Narrows a Failure to one of several codes — the multi-code sibling of Factory.is.

f
Failure.ignore(context: string): (error: unknown) => void

Builds a .catch() handler for a failure that genuinely has no consequence — but says so under QUNITX_DEBUG instead of vanishing. This is the raw handler; the ergonomic spelling at call sites is Task(promise).ignore(context), which wraps exactly this function.

f
Failure.is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.is(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.isFactory(value: unknown): value is FailureFactory<string, never>

True for a factory made by define. A factory carries the code it produces and its own is guard; a plain error-mapper carries neither, so an API can accept both in one position and tell them apart exactly, without guessing from arity.

f
Failure.isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.isFailure(value: unknown): value is Any

Whether value is a Failure — from this realm or any other.

f
Failure.observed(failure: Any): void

Reports a declared failure to the observation seam — onObserved plus the OBSERVED_CHANNEL_NAME channel. Task's consuming methods call this at their classification points; call it yourself only for a synchronous Result flow that never crossed a Task, so a tracing adapter sees those failures too.

f
Failure.onIgnored(observer: IgnoredObserver | null): void

Installs a process-wide observer for every ignored failure — the interception seam for "list everything the program decided not to handle". Costs one null check on the failure path only (nothing on success, nothing per Task), and retains nothing itself: whether the suppressed failures accumulate, sample, or stream somewhere is the observer's decision. Pass null to detach.

f
Failure.onObserved(observer: ObservedObserver | null): void

Installs a process-wide observer for every handled declared failure — the portable (browser-friendly) counterpart of subscribing to OBSERVED_CHANNEL_NAME. Single slot, one null check on the failure path, nothing retained; pass null to detach.

f
Failure.rootCause(error: unknown): unknown

The deepest cause in the chain — the original failure, whatever wrapped it since.

f
Failure.setDebug(enabled: boolean): void

Toggles ignored-failure reporting at runtime, overriding the QUNITX_DEBUG default.

f
Failure.toJSON(error: unknown): SerializedFailure

Converts a Failure (or any error) into plain JSON.

f
partition<O>(outcomes: ReadonlyArray<O>): { values: Exclude<O, Any>[]; errors: Extract<O, Any>[]; }

Splits outcomes into their successes and their failures, keeping both.

f
unwrap<O>(outcome: O): Exclude<O, Any>

Returns the success value, or throws the failure.

Interfaces

I
Failure.DefineOptions

Per-kind options accepted by define().

  • trace: (data: Data) => TraceAttributes

    Allowlist mapper from the typed payload to span/log attributes, consumed by attributes. Deliberately not a redaction filter: only what the mapper returns is ever exposed, so sensitive fields are private by omission, not by scrubbing.

I
Failure.FailureFactory

A callable failure constructor produced by define(), carrying its own type guard.

I
Failure.FailureOptions

Options accepted by the Failure constructor and by every generated factory.

  • cause: unknown

    The error this failure was derived from. Preserved verbatim and walked by causes().

  • stackAnchor: (...args: never[]) => unknown

    The function to truncate the stack at, so the top frame is the code that reported the failure rather than the plumbing that built it. Defaults to the constructor.

  • stackless: boolean

    Skips stack capture. Only worth setting for failures produced in a hot loop and consumed immediately — the capture, not the allocation, is what a Failure costs. See the performance section of the docs before reaching for it.

I
Failure.SerializedFailure

The wire form of a Failure — what toJSON emits and fromJSON accepts.

  • cause:
    SerializedFailure
    | { name: string; message: string; stack?: string; }

    The serialized cause chain: a nested Failure, or a plain error's identifying fields.

  • code: string

    The discriminant. Survives the wire, unlike a prototype.

  • data: unknown

    The structured payload, JSON round-tripped by toJSON so it cannot fail later.

  • failure: true

    Wire marker. Symbol.for keys survive neither JSON.stringify nor structuredClone, so the serialized form carries an explicit field in the brand's place.

  • message: string

    The human-readable sentence, already interpolated from data.

  • stack: string

    The producing process's stack. Frameless — the header line alone — when the failure was built { stackless: true }; the field itself is absent only if stack was never set.

Namespaces

N
Failure

Structured, discriminable failures — the rejection reason of a Task.

Type Aliases

T
Failure.Any = Failure<string, unknown>

Any Failure at all — the type to reach for when a signature accepts failures it does not enumerate, e.g. Result<T, Failure.Any> at a boundary that only logs.

T
Failure.IgnoredObserver = (
context: string,
error: unknown
) => void

The callback shape onIgnored installs: every ignored failure arrives with the label its call site declared.

T
Failure.ObservedObserver = (failure: Any) => void

The callback shape onObserved installs: every declared failure a consumer classified arrives right as it is handed to application code.

T
Failure.Of<F> = F extends FailureFactory<infer Code, infer Data> ? Failure<Code, Data> : never

The Failure type a factory produces: Failure.Of<typeof FileMissing>.

T
Failure.TraceAttributes = Record<string, string | number | boolean>

The attribute primitives tracing systems accept (OpenTelemetry's span-attribute values) — what a trace mapper returns and attributes yields.

T
Result<T, E = Any> = T | E

The success value or a declared failure — a bare union, discriminated by the Failure brand.

Variables

v
AwaitTimeout: FailureFactory<"AwaitTimeout", { ms: number; }>

The deadline in TaskClass#await elapsed before the work settled.

v
Failure.IGNORED_CHANNEL_NAME: "qunitx.failure.ignored"

Name of the diagnostics_channel that ignore publishes to on Node and Deno — subscribe with the platform API, no qunitx registry involved. Messages are { context: string, error: unknown }. Browsers have no channel; use onIgnored.

v
Failure.OBSERVED_CHANNEL_NAME: "qunitx.failure.observed"

Name of the diagnostics_channel that observed publishes to on Node and Deno. Messages are { error: Failure.Any }. This is what a tracing adapter subscribes to — e.g. OpenTelemetry, in its entirety:

v
Failure.Unknown: FailureFactory<"Unknown", { thrown: unknown; }>

The failure from() produces for a throwable that is not already a Failure.

v
T
Task: TaskConstructor

Call-or-construct, like Boolean/Date: Task(recipe) and new Task(recipe) build the same lazy Task. ES classes reject the call form, so the export is a Proxy whose apply forwards to construction — statics, instanceof, and the prototype all pass through.

lib/task/task.ts

Interfaces

I
RetryOptions

The full option bag for Task#retry; pass a RetryDelay directly when the delay is all you need.

  • delayMs: RetryDelay

    Wait between attempts. Omitted or 0 retries immediately, as it always has.

  • timeoutMs: number

    Deadline for each individual attempt. The attempt is abandoned when it fires — its signal goes off so cancellation-aware work stops — and counts as a failure like any other.

  • when: (
    error: unknown,
    attempt: number
    ) => boolean

    Which failures are worth another attempt. Returning false rethrows immediately, spending no further attempts and no delay.

Type Aliases

T
Executor<T> = (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void,
signal: AbortSignal
) => unknown

The new Promise shape, made lazy: settle imperatively through resolve/reject, with the Task's AbortSignal third.

T
Recipe<T> = () => T | PromiseLike<T>

The zero-parameter shape: return the value (or a promise of it) and that settles the Task. Declared with no parameters both because that is what it receives and because it is what lets TypeScript tell a recipe from an Executor — a lambda naming even one parameter can then only be the executor, so resolve gets contextually typed instead of any. A recipe that needs the AbortSignal takes the executor shape.

T
RetryDelay = number | ((attempt: number) => number)

Milliseconds to wait before the next attempt, or a function of the attempt just finished — the shape lib/job converged on for Oban-style backoff, with a constant as its degenerate case. (n) => Math.min(100 * 2 ** n, 30_000) is exponential; () => 100 is fixed.

Variables

v
AwaitTimeout: FailureFactory<"AwaitTimeout", { ms: number; }>

The deadline in TaskClass#await elapsed before the work settled.

v
T
Task: TaskConstructor

Call-or-construct, like Boolean/Date: Task(recipe) and new Task(recipe) build the same lazy Task. ES classes reject the call form, so the export is a Proxy whose apply forwards to construction — statics, instanceof, and the prototype all pass through.

lib/types.ts

Interfaces

I
BuildError

An esbuild failure, captured for display on the run's error page.

I
BuildState

One group's esbuild output and in-flight build bookkeeping, kept warm between watch-mode rebuilds. Lives at state.group.build, so it shares the group's lifetime rather than being threaded alongside the config as a second, independently-passable bag.

  • activeRebuild: Promise<void> | null

    Set when a parallel rebuild is in-flight during a watch-mode rerun. The /tests.js route awaits this before serving, so Chrome can navigate concurrently while esbuild finishes. Cleared by run after the build settles.

  • allTestCode: Buffer | string | null

    Full test bundle source, or null before the first build completes.

  • fallbackPage: FallbackPage | null

    Replaces the normal test page for this run, or null when the run renders tests as usual. The web server's / route serves this page and the Playwright page is navigated there. Cleared at the start of every new build attempt.

  • filteredTestCode: Buffer | string

    Bundle filtered to files that failed on the previous run (used by re-run mode).

  • htmlPathsToRunTests: string[]

    Absolute paths of every HTML file that will be opened in the browser to run tests.

  • lastBuildErrored: boolean

    true if the most recent build ended in an esbuild error. Keeps state.watch.lastBuildEndMs pinned to the last good build so a fix arriving after the error is never suppressed. Written by every run, read only in watch mode.

  • preBuildPromise: Promise<void> | null

    In-flight build promise started by run.ts before Chrome setup completes (initial run) or before run is called (reruns), so esbuild races navigation. Consumed and cleared by the first run() call.

I
ChromeHandle

Handles to a pre-launched Chrome, available synchronously the moment it is spawned — before the CDP endpoint is known. Enough to reap the process and its temp dir, which is all the process.on('exit') safety net and shutdownPrelaunch() need.

  • proc: ChildProcess

    The spawned Chrome child process.

  • shutdown: () => Promise<void>

    Kills Chrome and awaits async temp-dir cleanup. Safe to call before CDP is ready, and idempotent with Chrome's own dead-on-arrival cleanup. Call before process.exit().

I
Config

Full resolved qunitx configuration for a single run, merging package.json settings, CLI flags, and runtime state. Most fields are read-only after Config.setup() resolves; underscore-prefixed fields are mutable runtime slots populated during the run lifecycle.

  • browser: "chromium" | "firefox" | "webkit"

    Browser engine used for the test run ('chromium' | 'firefox' | 'webkit').

  • cwd: string

    Working directory this run resolves against: relative inputs, bare-specifier resolution inside the test bundle, and --before/--after hook paths. process.cwd() for the CLI; the JS API's cwd option otherwise. Distinct from projectRoot, which is wherever the nearest package.json sits — running from a subdirectory keeps that subdirectory's node_modules on the resolution chain, exactly as Node itself would.

  • extensions: string[]

    File extensions treated as test files (default: ['js', 'ts']).

  • failFast: boolean

    When true, abort the run after the first test failure (default: false).

  • fsTree: FSTree

    Current file-system snapshot, diffed in watch mode to detect added / removed files.

  • htmlPaths: string[]

    Absolute paths to HTML fixture files that wrap the compiled test bundle.

  • output: string

    Directory where the compiled test bundle and output HTML are written (default: 'tmp').

  • plugins: EsbuildPlugin[]

    Custom esbuild plugins applied during the test bundle build. Loaded from qunitx.config.{ts,js,mjs} in the project root. Common use cases: SFC formats like .vue (esbuild-plugin-vue-next), Svelte (esbuild-svelte), or any project-specific resolvers/loaders.

  • port: number

    TCP port the local test server listens on (default: 1234, auto-increments on conflict).

  • projectRoot: string

    Absolute path to the project root (directory containing package.json).

  • state: RunState

    Mutable state for this run; see RunState for the sharing rules.

  • testFileLookupPaths: string[]

    Paths searched when globbing for test files.

  • timeout: number

    Maximum milliseconds to wait for the full test suite before timing out (default: 20000).

  • webServer: HTTPServer

    The run's HTTP server, exposed purely as --before / --after hook surface — qunitx itself never reads it back. Hooks use it to register extra routes (mock APIs) before tests start.

I
Connections

Live handles for the three resources allocated at the start of a test run. Passed through the run pipeline and closed together on shutdown.

I
Counter

Running totals of test outcomes for a single test run. Mutated in place as TAP events arrive from the browser.

I
DaemonState

The daemon's persistent, cross-run handles, lent to a single run via RunState.daemon.

  • browser: Browser

    The daemon's Browser. run() reuses it instead of launching, and does not close it.

  • esbuildCache: EsbuildCache

    Persistent incremental-context slot, keeping the module graph warm across daemon runs.

  • pageSlot: { page: Page | null; }

    Persistent Page slot, reused across runs to save a newPage() (~70-130ms). Read through RunState.reusablePageSlot(), never directly — reuse is only valid for single-group runs.

I
EarlyChrome

A ChromeHandle plus the CDP endpoint, resolved once Chrome is listening.

I
EsbuildCache

A slot holding esbuild's incremental build context plus the key it was built for. Two lifetimes share this shape: the per-process one on BuildState (watch mode) and the daemon's persistent one, which survives across runs. buildIncrementally takes either — it disposes and recreates the context whenever the key changes.

I
FileCoverage

Per-source-file line coverage, accumulated across every executed bundle. Keyed by absolute source path. Lines are 1-based, matching editor/lcov conventions.

I
GroupState

RunState scoped to a single concurrent group — one fresh object per group of a run.

  • build: BuildState

    This group's bundle output and build bookkeeping.

  • groupMode: boolean

    true while running as one of several concurrent groups.

  • index: number

    Index within the run's group array; 0 for watch and single-group runs.

  • lastQUnitResult: QUnitResult | null

    QUNIT_RESULT delivered via the WS 'done' message; avoids a page.evaluate() CDP round-trip.

  • lastRanFiles: string[] | null

    Test files this group ran on the last run. Failure attribution falls back to this when a failing assertion's stack can't be resolved to one file — scoped per group so an unattributable failure blames only the files that group ran, not the whole invocation.

  • pendingConsoleHandlers: Set<Promise<void>> | null

    In-flight console handler promises; awaited before browser/page close so Firefox BiDi round-trips complete.

  • phase: "bundling" | "connecting" | "loading" | "running" | "done"

    Current lifecycle phase of this group's run.

  • selectors: QUnitSelector[] | undefined

    Exact test selections for this group, derived from lineTargets. Applied in the browser via QUnit.config.testFilter, which QUnit ANDs after filter/module. Per-group: each line-targeted file runs as its own group so untargeted files stay unfiltered.

  • signals: RunSignals

    Callbacks the run pipeline waits on, resolved as the browser reaches each milestone.

  • sourceMapDecoder: SourceMapDecoder | null

    Decoded inline source map for this group's bundle; resolves stack frames to original sources.

  • testEndCounts: Map<string, number>

    Tracks testEnd arrivals per test fullName in this group's run. Reset in lockstep with the run counter — explicitly NOT on every WS 'connection' event, which was the bug that broke no-html-test in CI run 26042614416.

  • wsConnectionCount: number

    Diagnostic-only: how many distinct WS connections this group's wss handler has accepted. Reset per WebServer.setup call. > 1 means the browser opened multiple WebSocket connections within one run — the prime suspect for the 2× test-execution flake (WS retry path in the injected runtime).

I
HtmlAssets

The run's resolved HTML fixtures and the assets they reference. Populated once by buildCachedContent and not written again, so every concurrent group can share one copy.

I
JUnitCase

One collected JUnit <testcase> — accumulated per testEnd and serialized into junit.xml at run end when --reporter=junit is active.

I
QUnitResult

The run summary the browser-side runtime publishes on window.QUNIT_RESULT.

I
RunGroup

One concurrent group of a run: the files bundled into a single page, and where that page's artifacts were written.

  • files: string[]

    Absolute paths of the test files this group bundled and ran together.

  • index: number

    Position in the run's group list, and the group-<index> suffix on output.

  • output: string

    Absolute path of this group's build output directory.

I
RunResults

Outcome totals and failure bookkeeping accumulated across every group of a single run. Every field here is mutated in place — see RunState for why replacement is unsafe.

  • aborted: boolean

    Whether the browser confirmed it dropped this run's remaining queue — qq, session.abort(), or an aborted signal. Lives here rather than on the group because groups share this object by reference, so one aborted group marks the whole run without any cross-group plumbing.

  • counter: Counter

    Running test-outcome counts, mutated in place as TAP events arrive.

  • coverage: CoverageFileMap | null

    Accumulator for per-source line coverage when coverage is enabled; null when it is off. Reassigned only by RunState.reset (a fresh Map per run), never by a group.

  • failedFiles: Set<string>

    Absolute paths of test files with ≥1 failure in the current run, attributed per-test via source maps. Every group adds into this one set; persisted to the failure cache at run end.

  • failedTests: FailedTestRecord[]

    Per-test metadata for the current run's failures; accumulated alongside failedFiles.

I
RunSignals

One-shot callbacks wiring the browser's progress back into the run pipeline. Each is installed by the code that awaits it and fired by the web server as the corresponding event arrives.

I
RunState

Mutable state for one test run, kept separate from the resolved settings on Config.

  • aborters: Set<() => void>

    One callback per live server that tells its connected pages to drop the rest of the QUnit queue — what qq, session.abort() and an aborted signal all go through.

  • console: Console

    Where this run's text goes: the TAP document, every reporter line, every # diagnostic and every forwarded page log. processConsole for the CLI, silentConsole for a programmatic run that only wants the result value. Shared by reference across concurrent groups.

  • daemon: DaemonState | null

    Non-null exactly when this run is executing inside the persistent daemon process — it is the daemon-mode flag as well as the handles. Daemon runs reuse the shared browser, suppress the per-connection TAP header, and leave that browser open at the end of the run.

  • group: GroupState

    RunState for this group only. The group spread replaces this object (everything else in RunState is shared by reference), so it is the one place per-group slots may live.

  • groupCount: number

    Number of concurrent groups in this run; 1 for watch and single-group runs.

  • groups: RunGroup[]

    How this run's files were split across those groups — the descriptive record RunResult reports. Assigned once per run alongside groupCount and shared by reference, so a group can read the whole split rather than only its own slice.

  • htmlAssets: HtmlAssets

    HTML fixtures and their referenced assets, resolved once by buildCachedContent before any group config is spread off. Frozen from that point on, so all groups share one copy.

  • reporters: Reporter[]

    Active reporter instances for this run, built by Reporters.create in Config.setup. One set for the whole run, so a stateful reporter sees every group rather than one slice.

  • results: RunResults

    Whole-run accumulators, shared by reference across every concurrent group.

  • signal: AbortSignal

    Cancels this run when it fires, or absent when nothing can. Carried here rather than threaded alongside the config because it is per-run input like everything else in state — and it is what lets a verb reach it from the resolved config instead of re-reading the raw arguments.

  • watch: WatchState

    File-watcher build bookkeeping. Only meaningful in watch mode, where there is one group.

I
WatchState

Build bookkeeping owned by the file watcher, used to decide whether a filesystem event should dispatch a rebuild. Watch mode runs exactly one group, so nothing here is contended.

  • building: boolean

    true while esbuild is actively compiling.

  • builtContentHash: Record<string, string>

    Per-file content hash of what was last dispatched to a build. Both the fs.watch change handler and the macOS/Deno rescan compare against this instead of mtime — mtime has 1-second resolution on some filesystems (macOS/HFS+), so rapid same-second writes with different content are indistinguishable by mtime; the hash catches them and drops echoes.

  • justAddedAt: Map<string, number>

    filePath → ms of when each file was last processed as an 'add', so a 'change' echo arriving inside ADD_SUPPRESS_WINDOW_MS can be suppressed.

  • justAddedFiles: Set<string>

    File paths added since the last build, used to decide whether a rebuild is needed.

  • lastBuildEndMs: number

    Timestamp (ms) of the most recent successful build, used for debounce logic. 0 before the first build.

  • pendingBuildTrigger: (() => void) | null

    Queued build-trigger callback; fires once the in-progress build completes.

Type Aliases

T
CoverageFileMap = Map<string, FileCoverage>

Absolute source path → its accumulated FileCoverage.

T
FallbackPage =
{ kind: "build-error"; error: BuildError; }
| { kind: "no-tests"; files: string[]; }

Why a run is showing something other than its tests: the last esbuild run failed, or every test file compiled but registered 0 QUnit tests (files holds their display paths).

T
FSTree = Record<string, null>

Snapshot of the project's file-system structure: a map of relative paths to null. Diffed against a fresh snapshot in watch mode to detect added or removed test files.

lib/utils/borrowed-globals.ts

Functions

f
borrowArgv(argv: string[]): Disposable

Replaces process.argv for the scope and restores the previous value on exit.

f
borrowEnv(overrides: Record<string, string | undefined>): Disposable

Applies overrides over process.env for the scope, then restores it exactly: keys added during the scope are dropped and changed values are put back.

lib/utils/close-with-grace.ts

Functions

f
closeWithGrace(
closes: Readonly<Record<string, Promise<unknown> | null | undefined>>,
graceMs?: number
): Promise<Abandoned>

Awaits every cleanup promise in closes, but never longer than graceMs. Resolves whichever happens first: every close settles (Promise.allSettled absorbs rejections so a single failing close cannot wedge the others), or the grace timer fires. Pending closes keep running in the background after a timeout — the caller is expected to process.exit() shortly after, which terminates them anyway.

Interfaces

I
Abandoned

What closeWithGrace gave up on, and a promise for those closes finally finishing.

  • names: string[]

    The keys still pending when the grace expired. Empty when everything settled in time.

  • settled: Promise<void>

    Settles when the abandoned closes do. Already settled when nothing was abandoned.

Variables

v
CLEANUP_GRACE_MS: 10000

Default grace period for the cleanup race — generous enough for a healthy shutdown of Playwright + HTTP server + Chrome pre-launch, well under the 60 s outer kill the test runner imposes, and tuned around Firefox + Windows where browser.close() is known to deadlock for the full 60 s.

lib/utils/color.ts

Functions

f
createColors(enabled: boolean)

Creates a set of ANSI color helpers with coloring enabled or disabled.

f
magenta(text?: string): string | MagentaReturn
2 overloads

ANSI magenta text. Call without arguments to chain: magenta().bold(text).

f
onBlue(text: string): string

The same on a blue ground — a 4xx, which is information rather than breakage.

f
onGreen(text: string): string

The terminal's own text on a green ground, padded — a 2xx.

f
onRed(text: string): string

The same on a red ground — a 5xx, or a request that never got an answer.

f
onYellow(text: string): string

The same on a yellow ground — a 3xx, or a measurement worth a second look.

Variables

v
colorEnabled: boolean

Whether this process should emit colour: the answer every helper below already uses.

lib/utils/convert-to-pascal-case.ts

Functions

f
convertToPascalCase(str: string): string

Converts a kebab-case, snake_case, camelCase, or PascalCase string to PascalCase. Splits on - and _ word boundaries; internal capitals in camelCase/PascalCase are preserved as-is.

lib/utils/exit-guard.ts

Functions

f
armExitGuard(
describe: () => string,
target?: ExitTarget,
write?: (message: string) => void
): ExitGuard

Makes exiting 0 something a command has to say, rather than what happens when nothing is left to do.

Interfaces

I
ExitGuard

What armExitGuard hands back: the one way to say the work produced a result.

I
ExitTarget

The parts of process the guard touches, so a test can drive its exit without exiting.

lib/utils/exit-on-signal.ts

Functions

f
exitOnSignal(arg_0?: { on?: (
signal: string,
handler: () => void
) => void
; exit?: (code: number) => void; }
): void

Turns a termination signal into an ordinary exit, so process.on('exit') handlers still run.

lib/utils/failure-cache.ts

Functions

f
build(config: Config): FailureCachePayload

Assembles the cache payload from the shared per-run failure slots on config.

f
filesToRerun(
projectRoot: string,
hasInputTargets: boolean,
fsTree: FSTree
): Promise<string[] | null>

Resolves the concrete previously-failing test files to re-run for --only-failed. Returns null when no cache exists (the caller picks the fallback — run all, or a full watch start). Otherwise returns the cached files that still exist, intersected with fsTree when input targets were given (so failures stay scoped to what the user asked for) or the full cached set when no targets were provided. Shared by the non-watch fsTree filter and the watch initial run.

f
read(projectRoot: string): Task<FailureCachePayload | null, never>

Reads the failure cache; returns null on a missing file or any parse/shape error.

f
record(
config: Config,
details: FailedTestDetails
): void

Records a failed testEnd into the shared per-run slots (state.results.failedFiles / failedTests) used to build the persistent cache. The failing file is attributed via source-map resolution of the first failing assertion's stack; when that can't be resolved to one of the run's test files (timeouts, no stack, a frame in a shared helper), the whole run/group set is added so a failure is never dropped from --only-failed.

f
write(
projectRoot: string,
cache: FailureCachePayload
): Promise<void>

Writes the failure cache. Best-effort; callers fire-and-forget like Timings.persist.

Interfaces

I
FailedTestRecord

One failed test, kept for display and future -t (test-name filter) wiring.

I
FailureCachePayload

On-disk shape of tmp/.qunitx-last-failures.json.

lib/utils/find-internal-assets-from-html.ts

Functions

f
findInternalAssetsFromHTML(htmlContent: string): string[]

Parses an HTML string and returns all internal (non-absolute-URL) <script src> and <link href> paths.

lib/utils/find-project-root.ts

Functions

f
findProjectRoot(cwd?: string): Task<string, ProjectRootNotFoundFailure>

Walks up from the working directory to the nearest package.json and resolves to its directory.

Type Aliases

T
ProjectRootNotFoundFailure = Failure.Of<ProjectRootNotFound>

The one failure findProjectRoot declares.

Variables

v
ProjectRootNotFound: Failure.FailureFactory<"ProjectRootNotFound", { cwd: string; }>

No package.json at or above the working directory, so there is no project to run in.

lib/utils/find-sidecar-esbuild.ts

Functions

f
findSidecarEsbuild(
execDir: string,
platform?: NodeJS.Platform
): string | null

Returns the path to an executable esbuild sidecar sitting next to execDir, or null if none is present. On Windows the .exe variant is preferred. Pure and injectable so the lookup order is unit-testable without a real compiled binary.

lib/utils/get-changed-file-paths-in-git-since.ts

Functions

f
getChangedFilePathsInGitSince(
projectRoot: string,
ref: string,
timeoutMs?
): Task<ChangeScan, GitScanFailure>

Resolves the working-tree paths that differ from ref, plus all uncommitted modifications/additions, as absolute paths — or scope: 'everything' when a blast-radius file (package.json, tsconfig*.json, …) changed.

f
runGit(
args: string[],
cwd: string,
timeoutMs?,
command?: string
): Task<string>

Runs one git command with a hard upper bound on how long it can take.

Type Aliases

T
GitScanFailure = Failure.Of<GitScanFailed>

The failure a git-change scan declares — the E of its Task, the Err of .result().

Variables

v
BLAST_RADIUS_FILES: Set

Exact basenames whose modification invalidates the entire test suite. A change to any of these short-circuits getChangedFilePathsInGitSince to null, signalling the caller to skip filtering and run everything. The qunitx config lives inside package.json per project convention, so package.json alone covers it.

v
BLAST_RADIUS_PATTERNS: RegExp[]

Basename regexes with the same blast-radius semantics. Currently catches tsconfig.json and editor variants like tsconfig.test.json / tsconfig.build.json.

v
GIT_TIMEOUT_MS: 30000

Upper bound on a single git invocation. --changed already degrades to "run all tests" when git fails, so a stuck git should reject rather than wedge the run — without a bound the CLI hangs forever, since neither git nor the caller ever gives up. 30s is orders of magnitude above a healthy git status on a large repo, so it only fires on a genuine wedge.

v
GitScanFailed

git could not answer: not a repo, unknown ref, git missing, or it exceeded timeoutMs.

lib/utils/get-changed-files.ts

Functions

f
getChangedFiles(
metafile: AffectedMetafile,
esbuildCwd: string,
changedAbsPaths: ReadonlySet<string>,
testFiles: readonly string[]
): Set<string>

Returns the subset of testFiles (absolute paths) whose transitive imports, per the cached esbuild metafile, include any file in changedAbsPaths.

Interfaces

I
AffectedMetafile

Subset of esbuild's Metafile shape that we actually read. Defined locally so this module doesn't import the heavy esbuild package — it only parses a JSON file that esbuild produced earlier.

lib/utils/html.ts

Functions

f
findScriptPlaceholder(html: string): string | undefined

Returns the explicit {{qunitxScript}} placeholder when it exists in the template.

f
injectScript(
html: string,
content: string
): string

Injects the qunitx runner script block into a dynamic HTML template.

f
isCustomTemplate(html: string): boolean

Reports whether an HTML template looks dynamic enough to act as a custom runner template.

lib/utils/import-playwright-core.ts

Functions

f
findSidecarPlaywrightCore(execDir: string): string | null

The file URL of a playwright-core shipped in <execDir>/node_modules, or null where none is.

f
importPlaywrightCore(): Promise<playwright-core>

playwright-core, from the running SEA's sidecar when it has one, else resolved as usual.

lib/utils/indent-string.ts

Functions

f
indentString(
string: string,
count?: number,
options?: { indent?: string; includeEmptyLines?: boolean; }
): string

Prepends count repetitions of indent (default: one space) to each non-empty line of string.

lib/utils/kill-process-group.ts

Functions

f
isTargetablePid(pid: number): boolean

Whether pid is safe to hand to a negated process.kill.

f
killProcessGroup(pid: number): void

Sends SIGKILL to a process and its entire process group. Requires the target to have been spawned with detached: true so that PGID === pid.

lib/utils/listen-to-keyboard-key.ts

Functions

f
listenToKeyboardKey(
inputString: string,
closure: (input: string) => void,
options?: { caseSensitive: boolean; }
): void

Registers a stdin listener that fires closure when the user types inputString (case-insensitive by default).

lib/utils/metafile-cache.ts

Functions

f
path(projectRoot: string): string

Returns the on-disk cache path for projectRoot. The path embeds a SHA-1 tag of the absolute project root so projects that share a hoisted/symlinked node_modules (pnpm workspaces, monorepos, integration test fixtures) write to distinct files. 12 hex chars is far below collision risk for the scale of "projects on one machine."

f
read(projectRoot: string): Task<MetafileCachePayload | null, never>

Reads the cached metafile. Returns null on miss or corruption.

f
write(
projectRoot: string,
esbuildCwd: string,
metafile: AffectedMetafile
): Promise<void>

Best-effort write; failures are swallowed because a cache miss on the next read just degrades to "run all tests."

Interfaces

I
MetafileCachePayload

Persistent on-disk cache of the most recent successful esbuild metafile, used by --changed / --since to compute the reverse-dependency graph without re-running esbuild. Lives under node_modules/.cache/ (npm convention, gitignored, survives rm -rf tmp/).

lib/utils/open-output-in-browser.ts

Functions

f
openOutputInBrowser(config: Config): Task<void, never>

Opens the test output in the browser qunitx uses, detached from the qunitx process. In watch mode, opens the live HTTP server URL so WebSocket-driven reloads work on file changes. In normal mode, opens the static file:// URL (the bundle is self-contained, no server needed). If config.open is a string, it is used as the browser binary/command directly (e.g. 'brave', 'google-chrome-lts').

lib/utils/path-exists.ts

Functions

f
pathExists(path: string): Task<boolean, never>

Returns true if the given filesystem path is accessible, false otherwise.

lib/utils/perf-log.ts

Functions

f
perfLog(
label: string,
...details: unknown[]
): void

Writes a timestamped perf trace line to stderr when --trace-perf is active.

lib/utils/read-template.ts

Functions

f
readTemplate(relativePath: string): Promise<string>

Reads a template file by relative path. Two runtimes to satisfy:

lib/utils/run-user-module.ts

Functions

f
isDenoCompiledBinary(
hasDeno?: boolean,
execPath?: string
): boolean

True when running inside a deno compiled binary (vs deno run script.ts or plain Node). Detection is on process.execPath: under deno run it ends with deno (or deno.exe on Windows); inside a compiled binary it's the user's binary name. Deno.mainModule looked tempting but is also a file: URL in compiled binaries (a virtual path under /tmp/deno-compile-<name>/), so it can't differentiate the two.

f

Type Aliases

T
UserScriptFailedFailure = Failure.Of<UserScriptFailed>

The one failure runUserModule declares.

Variables

v
UserScriptFailed

A user-supplied --before/--after script threw, or could not be imported.

lib/utils/search-in-parent-directories.ts

Functions

f
searchInParentDirectories(
directory: string,
targetEntry: string
): Promise<string | undefined>

Recursively searches directory and its ancestors for a file or folder named targetEntry; returns the absolute path or undefined.

lib/utils/source-map.ts

Functions

f
decodeMappings(mappings: string): Segment[][]

Parses the compact mappings string from a source-map V3 object into a per-line array of segments, sorted by generatedCol (esbuild always emits them in order).

f
extractInline(
bundle: ArrayBufferView | string | null,
outDir: string
): SourceMapDecoder | null

Extracts and decodes the inline source map that esbuild appends to a bundle as //# sourceMappingURL=data:application/json;base64,…. Returns null when no inline map is present or parsing fails.

f
isBundleUrl(url: string): boolean

Returns true when url points to a test bundle (/tests.js or /filtered-tests.js) served by the local HTTP server.

f
lookupPosition(
decoder: SourceMapDecoder,
generatedLine: number,
generatedCol: number
):
{ absolutePath: string; line: number; col: number; sourceText: string | null; }
| null

Returns the original source position for a generated (line, col) pair. Both coordinates are 1-based (the format Chrome uses in Error.stack). Returns null when the position cannot be mapped.

f
parse(
json: string,
outDir: string
): SourceMapDecoder

Parses a source-map V3 JSON string into a SourceMapDecoder ready for position lookup.

f
parseFrameLocation(text: string): { url: string; line: number; col: number; } | null

Parses the trailing URL:LINE:COL suffix of a stack frame. The greedy (.+) group captures URLs that contain colons (e.g. http://host:PORT/path); the regex anchors mean the last two :digits sequences are always the line/col.

f
readVLQ(
text: string,
position: number
): [number, number]

Reads one VLQ-encoded integer from text starting at position. Returns [decodedValue, positionAfterLastConsumedChar].

f
resolveFrame(
frame: string,
decoder: SourceMapDecoder,
projectRoot: string
):
{ resolved: string; userPath: string | null; sourceText: string | null; }
| null

Attempts to resolve a single stack-frame line to its original source location. Handles Chrome named (at FUNC (URL:L:C)), Chrome anonymous (at [async] URL:L:C), and Firefox/WebKit (FUNC@URL:L:C) formats.

f
sourceAbsolutePath(
decoder: SourceMapDecoder,
sourceIndex: number
): string | null

Resolves the absolute path of the source at sourceIndex in the map's sources array, applying the same sourceRoot + outDir rules as position lookups. Returns null when the index is out of range or the entry is empty. Used by the coverage collector to key accumulated line coverage by absolute source path.

Interfaces

I
Segment

One decoded mapping entry. All coordinates are 0-based.

I
SourceMapDecoder

Parsed representation of a source-map V3 JSON, ready for position lookup.

lib/utils/time-counter.ts

Functions

f
start(): { startTime: Date; stop: () => number; }

Returns a timer object with a startTime Date and a stop() method that returns elapsed milliseconds.

lib/utils/with-loop-alive.ts

Functions

f
withLoopAlive<T>(work: () => Promise<T>): Promise<T>

Runs work with the event loop held open, so it cannot end by everything going quiet.

lib/web/index.ts

Classes

c
HTTPServer()

Minimal HTTP + WebSocket server used to serve test bundles and push reload events.

Interfaces

I
Request

The request a route handler receives: node's, plus the four conveniences this server attaches before dispatching.

I
Response

The response a route handler receives: node's, plus a JSON shorthand.

I
Route

One registered route: the parsed shape get()/post()/… store and #findRouteHandler matches against.

Type Aliases

T
Middleware = (
req: Request,
res: Response,
next: () => void
) => void

Middleware function signature — call next() to continue the chain.

T
RouteHandler = (
req: Request,
res: Response
) => unknown | Promise<unknown>

Route handler function signature for registered GET/POST/etc. routes.

Variables

v
MIME_TYPES: Record<string, string>

Map of file extensions to their corresponding MIME type strings.

README.md

qunitx-cli