plgg-test
The family's own test runner, built from scratch on plgg so the monorepo can drop its dependency on a large third-party test framework (and the recurring Dependabot churn that comes with it).
Unlike a traditional runner, plgg-test is pipe-style: an assertion is a data-last function you pipe a value through, returning a branded plgg Result. A test body returns its assertion and the runner collects it. There is no fluent expect(x).toBe(y) chain and no throw-on- mismatch — the test API reads exactly like the rest of plgg.
Writing a test
The common case is one check call — check(actual, ...matchers):
import { test, check, toBe } from "plgg-test";
test("adds", () => check(2 + 2, toBe(4)));toBe(4) is a data-last matcher ((actual) => Assertion); check applies it to the actual and returns the verdict. Several matchers on one value compose — and every failure is reported, not just the first:
import { test, check, toBe, toBeGreaterThan } from "plgg-test";
test("checks many properties at once", () =>
check(42, toBe(42), toBeGreaterThan(10)));For several independent assertions, return them with all (which aggregates every failure as siblings, the way plgg's cast accumulates validation errors). This is a real, unedited spec from the plgg package:
import {
test,
check,
all,
toBe,
okThen,
shouldBeErr,
} from "plgg-test";
import {
isAlphanumeric,
asAlphanumeric,
box,
} from "plgg/index";
test("isAlphanumeric and asAlphanumeric basic validation", () =>
all([
check(
isAlphanumeric(box("Alphanumeric")("test123")),
toBe(true),
),
check(
asAlphanumeric("abc123"),
okThen((b) => toBe("abc123")(b.content)),
),
check(asAlphanumeric("test-123"), shouldBeErr()),
]));Narrowing is data-flow, not control-flow
The traditional assert(isOk(r)); r.content narrows by throwing. plgg-test has no throwing assertion. Instead the value-carrying matchers (shouldBeOk, shouldBeErr, shouldBeSome, …) unwrap the inner value into the pipeline, so you keep composing with plgg's own cast:
import { test, shouldBeOk, toBe } from "plgg-test";
import { cast, asInt, int } from "plgg";
test("parses", () =>
cast(asInt("42"), shouldBeOk(), toBe(int(42))));shouldBeOk() yields the unwrapped value on success (and a Fail otherwise), which toBe(int(42)) then checks — a single plgg pipeline, no as, no escape hatch.
Combinators
not inverts a matcher; all aggregates; allAsync awaits a list of async assertions; async bodies are just proc chains. They are functions, not .not/.resolves chain segments.
Running it
The CLI scans source roots for *.spec.ts. The plgg package wires it into its npm scripts:
# one-shot run (tsc gate first, then the suite)
plgg-test src
# re-run on every change (a fresh process per run,
# so source edits are always reflected)
plgg-test src --watch
# four-metric V8 coverage (statements/branches/functions/
# lines), gated per package — zero third-party deps
plgg-test src --coverageThe process exits non-zero on any failure (or zero discovered tests), so it is a correct CI / agent gate.
Trust by demonstration
An assertion is a branded Result (tagged with plgg's own Box), so the runner can tell a real verdict from a domain Result a body happens to return: a body that doesn't return a genuine assertion fails rather than passing vacuously — the false-green hole a return-based model would otherwise open. The drop-shape cases (forgotten return, un-awaited promise, swallowed Err, …) are each proven to fail by a plain-throw meta-harness that doesn't trust the code under test.