class
AssertionError
export class AssertionError extends Error { ... }Thrown when a matcher does not hold.
API reference
The test API and worker for `uf test`: describe/it, a full matcher set, and the process uf fans test files out to.
Written from the source by uf doc when this site was built: the signature and the comment above each export, grouped by the specifier a program imports it from.
@uniflowed/testclass
AssertionErrorexport class AssertionError extends Error { ... }Thrown when a matcher does not hold.
type
Matchersexport type Matchers<R> = {
readonly toBe: (expected: mixed) => R,
readonly toEqual: (expected: mixed) => R,
readonly toStrictEqual: (expected: mixed) => R,
readonly toBeTruthy: () => R,
readonly toBeFalsy: () => R,
readonly toBeNull: () => R,
readonly toBeUndefined: () => R,
readonly toBeDefined: () => R,
readonly toBeNaN: () => R,
readonly toBeGreaterThan: (expected: number) => R,
readonly toBeGreaterThanOrEqual: (expected: number) => R,
readonly toBeLessThan: (expected: number) => R,
readonly toBeLessThanOrEqual: (expected: number) => R,
readonly toBeCloseTo: (expected: number, digits?: number) => R,
readonly toContain: (expected: mixed) => R,
readonly toContainEqual: (expected: mixed) => R,
readonly toHaveLength: (expected: number) => R,
readonly toHaveProperty: (path: string, ...rest: $ReadOnlyArray<mixed>) => R,
readonly toMatch: (expected: string | RegExp) => R,
readonly toMatchObject: (expected: mixed) => R,
readonly toBeInstanceOf: (expected: mixed) => R,
readonly toBeTypeOf: (expected: TypeName) => R,
readonly toSatisfy: (predicate: (value: mixed) => boolean) => R,
readonly toMatchSnapshot: (hint?: string) => R,
readonly toMatchInlineSnapshot: (expected?: string) => R,
readonly toThrow: (...rest: $ReadOnlyArray<mixed>) => R,
readonly toHaveBeenCalled: () => R,
readonly toHaveBeenCalledTimes: (count: number) => R,
readonly toHaveBeenCalledWith: (...args: $ReadOnlyArray<mixed>) => R,
readonly toHaveBeenLastCalledWith: (...args: $ReadOnlyArray<mixed>) => R,
readonly toBeInTheDocument: () => R,
readonly toBeVisible: () => R,
readonly toBeDisabled: () => R,
readonly toBeEnabled: () => R,
readonly toBeChecked: () => R,
readonly toBeRequired: () => R,
readonly toHaveFocus: () => R,
readonly toHaveAttribute: (name: string, value?: mixed) => R,
readonly toHaveClass: (...names: $ReadOnlyArray<string>) => R,
readonly toHaveTextContent: (expected: string | RegExp) => R,
readonly toHaveValue: (expected: mixed) => R,
/**
* Run axe-core over this element's subtree and require it to find nothing.
*
* `Promise<void>` rather than `R`, and it is the one matcher in this listing
* that is not generic: axe has no synchronous entry point, so the answer is
* a promise however the expectation was reached, and `await` is not optional.
*
* await expect(container).toHaveNoAxeViolations();
* await expect(container).toHaveNoAxeViolations({ tags: ["wcag2a"] });
*
* The rule set comes from `accessibility.axe` in `uf.config.js`; the argument
* narrows it for one assertion. See `./axe.js`.
*/
readonly toHaveNoAxeViolations: (options?: AxeOptions) => Promise<void>,
readonly not: Matchers<R>,
...
};Every matcher, each returning R.
Generic in the return type because the surface exists twice and that is the only thing that differs: expect(x) raises where a matcher fails and hands back nothing, while expect(p).resolves settles first and so hands back a promise. Writing the forty-one names once and saying what changes is the whole reason for the parameter — the alternative was the same list twice, with => void on one copy and => Promise<void> on the other, and a reader left to diff them.
Where an argument is mixed it is because the runtime genuinely takes anything there and the checker would be lying to say otherwise: toEqual accepts an asymmetric matcher standing in for a value at any depth, and toHaveValue compares whatever a control is holding. Where it is not — toHaveLength wants a number, toMatch a string or a pattern, toBeTypeOf one of the eight words typeof produces — the narrower type is what the implementation already assumes, and saying it out loud is the point of the exercise.
not is the same list again because negation is the only thing it changes. resolves and rejects are deliberately not here: they belong to [Expectation], because expect(p).resolves.not exists and expect(x).not.resolves does not.
type
Expectationexport type Expectation = Matchers<void> & {
readonly resolves: Matchers<Promise<void>>,
readonly rejects: Matchers<Promise<void>>,
...
};What expect(received) hands back.
The matchers, plus the two that settle a promise before applying them. An intersection rather than a copy of the list with two lines added, and rather than an object spread, because a spread of an object type drops the readonly off every property it carries over — Flow computes a fresh object from the spread and the fresh one is writable, which would publish forty-one assignable matchers.
# What is still not checked
expect(5).resolves types, and fails when it runs. Saying otherwise needs expect to have two call signatures — one for a promise handing back a shape with resolves, one for everything else handing back a shape without — and Flow then requires the single function behind them to satisfy both, which no single function does. The overload is written down here rather than attempted because "it did not type" is the kind of thing that gets tried twice.
type
Expectexport type Expect = {
(received: mixed): Expectation,
// `flow/unclear-type` reads source text rather than an AST, and the shape it
// recognises as a property key rather than a type is a name at the start of
// a line or straight after `{`, `,` or `;`. `readonly any:` is neither, so
// the rule reports Jest's, Vitest's and Sinon's name for this matcher as an
// `any` type. The rule's own comment already lists `@uniflowed/test`'s
// `expect.any` among the false positives it exists to avoid; this is the one
// spelling it still cannot see past.
// uf-lint-disable-next-line flow/unclear-type
readonly any: (constructor: mixed) => AsymmetricMatcher,
readonly anything: () => AsymmetricMatcher,
readonly objectContaining: (expected: interface {}) => AsymmetricMatcher,
readonly arrayContaining: (expected: $ReadOnlyArray<mixed>) => AsymmetricMatcher,
readonly stringContaining: (substring: string) => AsymmetricMatcher,
readonly stringMatching: (pattern: string | RegExp) => AsymmetricMatcher,
readonly closeTo: (value: number, digits?: number) => AsymmetricMatcher,
readonly not: {
readonly objectContaining: (expected: interface {}) => AsymmetricMatcher,
readonly arrayContaining: (expected: $ReadOnlyArray<mixed>) => AsymmetricMatcher,
readonly stringContaining: (substring: string) => AsymmetricMatcher,
readonly stringMatching: (pattern: string | RegExp) => AsymmetricMatcher,
readonly closeTo: (value: number, digits?: number) => AsymmetricMatcher,
...
},
...
};expect itself: callable, and carrying the matchers that stand in for a value instead of being one.
Inexact, and it has to be. The value is a function, every function has name, length, call, apply and bind, and an exact object type refuses one for exactly that reason. Inexactness costs nothing that matters here: Flow still reports a read of a property this type does not list, which is what makes expect.anythign() an error.
variable
expectexport const expect: Expect = expecting();Assert about a value.
The expect.* half are the matchers that stand in for a value instead of being one. expect(user).toEqual({ id: expect.any(String), name: "uf" }) says what a test means; spelling out the id would either be a lie or a second source of truth. They work at any depth, because equals asks every value it meets whether it is one.
expect.not.* is the negated form, spelled the way Jest and Vitest spell it — expect.not.objectContaining({ error: expect.anything() }) reads better than a negated assertion around the whole object, and is the form a suite being ported will already have.
type
TestBodyexport type Body = () => mixed | Promise<mixed>;What a test or hook body may return.
type
Modifierexport type Modifier = "none" | "only" | "skip" | "todo";The suffix written on a registration call.
type
BenchOptionsexport type BenchOptions = {|
readonly warmup?: number,
readonly iterations?: number,
readonly timeout?: number,
|};How a benchmark runs under uf test --bench.
warmup calls are made and their times thrown away, then iterations calls are each timed. timeout is the budget for one call, in milliseconds, and the benchmark as a whole is held to that budget once per call.
type
Caseexport type Case = {|
readonly kind: "test" | "bench",
readonly name: string,
readonly body: Body | null,
readonly modifier: Modifier,
readonly skipReason: string | null,
readonly timeoutMs: number | null,
/** How to run it, for a benchmark; `null` for a test. */
readonly bench: BenchOptions | null,
readonly line: number,
readonly column: number,
|};One registered test case, or one benchmark.
type
Suiteexport type Suite = {|
readonly kind: "suite",
readonly name: string,
readonly modifier: Modifier,
readonly children: Array<Suite | Case>,
readonly beforeAll: Array<Body>,
readonly afterAll: Array<Body>,
readonly beforeEach: Array<Body>,
readonly afterEach: Array<Body>,
readonly line: number,
readonly column: number,
|};One describe and everything inside it.
type
TestOptionsexport type TestOptions = {| readonly timeout?: number |};Options a single test may carry.
variable
describeexport const describe: $FlowFixMe = suiteApi();Group tests, and scope hooks to them.
describe.only, describe.skip and describe.todo apply the modifier to everything inside; describe.each(table) declares one suite per row.
variable
itexport const it: $FlowFixMe = caseApi();Register one test.
it.only, it.skip and it.todo do what they say; it.each(table) runs the body once per row, with %s and %j in the name replaced by the row.
variable
testexport const test: $FlowFixMe = it;test is it, for people who write it that way.
variable
benchexport const bench: $FlowFixMe = benchApi();Register one benchmark.
uf test reports a benchmark as skipped, so a suite does not pay for timing one, and uf test --bench runs the benchmarks in place of the tests and reports how long each call took. bench.only, bench.skip and bench.todo do what it's do. See [BenchOptions] for warmup, iterations and timeout.
function
beforeAllexport function beforeAll(body: Body): void { ... }Run once before the first test in this suite that runs.
function
afterAllexport function afterAll(body: Body): void { ... }Run once after the last test in this suite that ran.
function
beforeEachexport function beforeEach(body: Body): void { ... }Run before every test in this suite and its children.
function
afterEachexport function afterEach(body: Body): void { ... }Run after every test in this suite and its children, including failures.
type
ModuleNamespaceexport type ModuleNamespace = { +[string]: mixed };The shape of a module's exports, as far as a type can say it.
type
ModuleFactoryexport type ModuleFactory<Module> = () => Partial<Module> | Promise<Partial<Module>>;What a uft.mock factory hands back.
Partial<Module> rather than Module because a partial mock is the common case — replace send, keep everything else — and rather than an unconstrained object because the whole point of a Flow-first toolchain doing this is that { send: 42 } for a module whose send is a function is a type error at the call rather than a TypeError three tests later.
type
Uftexport type Uft = {
readonly fn: typeof fn,
readonly spyOn: typeof spyOn,
readonly mocked: typeof mocked,
readonly clearAllMocks: typeof clearAllMocks,
readonly resetAllMocks: typeof resetAllMocks,
readonly restoreAllMocks: typeof restoreAllMocks,
readonly stubEnv: typeof stubEnv,
readonly unstubAllEnvs: typeof unstubAllEnvs,
readonly stubGlobal: typeof stubGlobal,
readonly unstubAllGlobals: typeof unstubAllGlobals,
readonly waitFor: typeof waitFor,
readonly waitUntil: typeof waitUntil,
readonly useFakeTimers: typeof timers.installFakeClock,
readonly useRealTimers: typeof timers.restoreRealClock,
readonly isFakeTimers: typeof timers.isFaked,
readonly advanceTimersByTime: typeof timers.advanceTimersByTime,
readonly advanceTimersByTimeAsync: typeof timers.advanceTimersByTimeAsync,
readonly advanceTimersToNextTimer: typeof timers.advanceTimersToNextTimer,
readonly runAllTimers: typeof timers.runAllTimers,
readonly runOnlyPendingTimers: typeof timers.runOnlyPendingTimers,
readonly getTimerCount: typeof timers.getTimerCount,
readonly setSystemTime: typeof timers.setSystemTime,
readonly getMockedSystemTime: typeof timers.getMockedSystemTime,
readonly mock: typeof mockModule,
readonly doMock: typeof mockModule,
readonly unmock: typeof unmockModule,
readonly doUnmock: typeof unmockModule,
readonly importActual: typeof importActualModule,
readonly importMock: typeof importMockModule,
readonly resetModules: typeof resetModulesNow,
};The uft namespace's type.
Written out member by member rather than left as one $FlowFixMe, because the module-mocking half of it is the half a type can genuinely check: a factory that hands back the wrong shape for the module it is standing in for is an error at the call site, and that only works if uft has a type at all. The members that were already typed loosely keep the types they have — typeof reads them from their definitions, so this list cannot drift from them.
variable
uftexport const uft: Uft = Object.freeze({
fn,
spyOn,
mocked,
clearAllMocks,
resetAllMocks,
restoreAllMocks,
stubEnv,
unstubAllEnvs,
stubGlobal,
unstubAllGlobals,
waitFor,
waitUntil,
// The clock a test controls. A test about "after five minutes the session
// expires" should not take five minutes.
useFakeTimers: timers.installFakeClock,
useRealTimers: timers.restoreRealClock,
isFakeTimers: timers.isFaked,
advanceTimersByTime: timers.advanceTimersByTime,
advanceTimersByTimeAsync: timers.advanceTimersByTimeAsync,
advanceTimersToNextTimer: timers.advanceTimersToNextTimer,
runAllTimers: timers.runAllTimers,
runOnlyPendingTimers: timers.runOnlyPendingTimers,
getTimerCount: timers.getTimerCount,
setSystemTime: timers.setSystemTime,
getMockedSystemTime: timers.getMockedSystemTime,
// Module interception. `doMock` is `mock` and `doUnmock` is `unmock`, under
// the names Vitest gives the un-hoisted forms: there is one form here,
// because uf hoists neither, and a `doMock` that was a different function
// would be claiming a difference that does not exist.
mock: mockModule,
doMock: mockModule,
unmock: unmockModule,
doUnmock: unmockModule,
importActual: importActualModule,
importMock: importMockModule,
resetModules: resetModulesNow,
});The uft namespace.
A frozen object rather than a class: it is a namespace, nothing about it is per-instance, and freezing it means a test cannot leave a monkey-patch behind for the next one.
type
Outcomeexport type Outcome =
| {|
readonly status: "passed",
/**
* One timing per measured call, in whole microseconds, for a benchmark
* run under `uf test --bench`.
*/
readonly samples?: $ReadOnlyArray<number>,
|}
| {|
readonly status: "failed",
readonly message: string,
readonly stack: string | null,
readonly expected: string | null,
readonly received: string | null,
/** Where the failing assertion was written, when the stack says. */
readonly site: {| readonly line: number, readonly column: number |} | null,
|}
| {|
readonly status: "skipped",
readonly reason: "explicit" | "not-only" | "filtered" | "bench" | "not-bench",
readonly message?: string | null,
|}
| {| readonly status: "todo" |};How one case ended.
type
Resultexport type Result = {|
readonly name: string,
readonly line: number,
readonly column: number,
readonly durationMicros: number,
readonly outcome: Outcome,
|};One finished case, as the runner reports it.
type
RunOptionsexport type RunOptions = {|
/** Keep only cases whose full name contains this, reporting the rest skipped. */
readonly filter?: string | null,
/** Wall-clock budget for one case, in milliseconds. */
readonly timeoutMs?: number,
/**
* Absolute path of the file being run.
*
* Snapshots live beside the file that took them, so the runner has to say
* which file that is — a test's name alone does not locate it.
*/
readonly file?: string,
/**
* Run the benchmarks and report the tests skipped, rather than the other way
* round. `uf test --bench` sets it.
*/
readonly bench?: boolean,
|};How a run is configured.
variable
DEFAULT_TIMEOUT_MSexport const DEFAULT_TIMEOUT_MS: number = 5000;Default budget for one case, matching what most runners use.
variable
NAME_SEPARATORexport const NAME_SEPARATOR: string = " > ";The separator between a suite's name and its child's.
type
Siteexport type Site = {| readonly line: number, readonly column: number |};A position in a source file, one-based line and column.
type
SpyCallexport type SpyCall = {
readonly args: $ReadOnlyArray<mixed>,
readonly returned?: mixed,
readonly threw?: mixed,
};One call: what went in, and what came out.
type
SpyResultexport type SpyResult =
| { readonly type: "return", readonly value: mixed }
| { readonly type: "throw", readonly value: mixed };One call's outcome, in the shape Vitest reports it.
function
fnexport function fn(implementation?: mixed): $FlowFixMe { ... }A spy with no original behind it.
fn() records and returns undefined; fn(body) records and runs body.
function
spyOnexport function spyOn(object: mixed, method: string): $FlowFixMe { ... }Replace object[method] with a spy that calls through to it.
Calls through by default, which is what makes spyOn an observation rather than a replacement — a test that only wants to know a method was called does not have to reimplement it. mockImplementation is how a test says it wants the other thing.
The original is put back by mockRestore, and by uft.restoreAllMocks.
type
Strictnessexport type Strictness = "loose" | "strict";How strictly two values are compared.
function
equalsexport function equals(
left: mixed,
right: mixed,
seen: Array<Pair> = [],
strictness: Strictness = "loose",
): boolean { ... }Whether left and right are structurally equal.
seen carries the pairs currently being compared, which is what makes a cyclic structure terminate.
class
RunawayTimersErrorexport class RunawayTimersError extends Error { ... }Raised when a run loop will not terminate.
@uniflowed/test/in-sourcevariable
IN_SOURCE_GLOBALexport const IN_SOURCE_GLOBAL: "__ufInSourceTests" = "__ufInSourceTests";The name uf compiles import.meta.uf.test into a call on.
Exported so the worker and the tests name it once; it is spelled out in crates/uf_transform/src/print.rs as well, and tests/library/in-source.test.js is what keeps the two spellings equal.
type
InSourceTestsexport type InSourceTests = {
readonly describe: typeof describe,
readonly it: typeof it,
readonly test: typeof test,
readonly expect: Expect,
readonly beforeAll: typeof beforeAll,
readonly beforeEach: typeof beforeEach,
readonly afterAll: typeof afterAll,
readonly afterEach: typeof afterEach,
readonly fn: typeof fn,
readonly spyOn: typeof spyOn,
readonly uft: Uft,
};What an in-source block is handed.
The subset of @uniflowed/test a test body needs, and deliberately not all of it: a block that wants module mocking or a snapshot is a block that has outgrown living inside the file it tests, and import { uft } from "@uniflowed/test" in a file of its own is the answer to that. uft is here anyway because spies and the fake clock are ordinary for a unit test.
function
installInSourceTestsexport function installInSourceTests(url: string): () => void { ... }Hand in-source blocks their API for the file url, and nothing to any other module.
Returns the function that puts the global back as it was, which the worker calls when the file is over. Restoring rather than leaving it set is the same rule the worker applies to environment stubs and module mocks: a file may not change what the next file on this worker sees.
@uniflowed/test/appfunction
createTestAppexport function createTestApp(options: AppTestOptions): Promise<TestApp> { ... }Start the application's real web pipeline in an isolated process for uf test.
@uniflowed/test/browserfunction
createBrowserexport function createBrowser(options?: BrowserOptions): Promise<TestPage> { ... }An isolated Chromium page; the test harness stays alive across navigations.