@aweftjs/testing
The checks the rest of the stack is held to: the conformance suite, a model reading of the format to check real implementations against, the tier rule, and seeded randomness.
This package is an integrator. It sits outside the tier ordering because everything is allowed to depend on it, and nothing it exports ships to a user of the library. It is where a claim about the stack becomes a check that fails when the claim stops being true.
Conformance
A fixture is one case stated as plain JSON: the bytes, what they mean, the document before and the document after. Nothing about this repo's types is needed to read one, which is the point. An implementation in another language reads spec/fixtures/ and is conformant when it agrees with every file there.
import { checkFixture, type Fixture } from '@aweftjs/testing';
const fixture: Fixture = JSON.parse(readFileSync('spec/fixtures/001-object-slots.json', 'utf8'));
checkFixture(fixture); // throws naming the fixture and the check that failedcheckFixture runs the case both directions. It decodes the stated bytes and compares the deltas to the stated JSON, encodes the stated JSON and compares to the stated bytes, applies the commits and compares the document to the stated ending, and re-runs each commit with its own deltas shuffled and reversed to check that the order they arrive in changes nothing.
The commits themselves keep their order. Section 4 of the format requires that, and only the deltas inside one commit are unordered.
The encode direction matters more than it looks. Bytes checked only by decoding them are checked against the package that produced them, so the fixture asks the implementation whether it agrees with itself. The stated JSON is authored (the documents by hand, the delta order by the generator's own rule), so encoding it asks a question the decoder's own output cannot answer. The bytes in a fixture were produced by the reference encoder when the fixture was generated; what anchors that encoder to the prose of the format is a pair of commits spelled out by hand, head by head, in codec's own tests. A second implementation proves itself by agreeing with those fixtures byte for byte.
checkInvalidFixture is the other half. Each case in spec/fixtures/invalid/ names a reason and a stage, and an implementation that refuses the input for a different reason has not agreed on the format, it has agreed on rejecting one string.
import { checkInvalidFixture, type InvalidFixture } from '@aweftjs/testing';
checkInvalidFixture(JSON.parse(readFileSync('spec/fixtures/invalid/011-truncated.json', 'utf8')));Checking a real implementation
Both check functions take an Applier: one reading of the format, as a function from a starting document and some commits to the document reached.
type Applier = (initial: DocumentJson, commits: readonly Commit[]) => DocumentJson;A fixture states its commits as CommitJson, which is bytes plus JSON. Your applier is never handed those: checkFixture decodes each one and calls you with Commit, the codec type, with real byte-string ids and reference objects. Read the format from spec/format.md and the codec types, not from the fixture JSON shape.
modelApplier is the default, and it is the harness's own reading: plain data, no reactivity, written from the specification rather than from any package. Passing a second applier is how a real implementation gets held to the same suite:
checkFixture(fixture, myApplier);Two independent readings reaching the same document from the same bytes is the evidence. One implementation agreeing with itself is not.
When your applier throws on a fixture that is valid, the failure names the fixture, the delta order it was running, and the reason you threw. When it returns the wrong document, the failure prints both documents. Those are the two ways a second reading goes wrong, and the suite is built to tell them apart.
The document model
DocumentJson is flat: observables keyed by id in text form, with the root named separately. Flat rather than nested because an observable can be named from more than one place, and a nested spelling would have to pick one and quietly lose the others.
import { applyCommit, canonicalJson } from '@aweftjs/testing';
const after = applyCommit(before, commit); // a new document, the input untouched
canonicalJson(after) === canonicalJson(expected) // how two documents are comparedapplyCommit checks every delta before applying any, which is what makes a commit atomic: a commit that breaks a rule leaves the document exactly as it was. Compare documents through canonicalJson rather than directly, or the order keys happened to be inserted in becomes part of the answer.
The recording host
recordingDocument() is a light document from @aweftjs/dom that writes down every node operation, so a test asserts what a mount did to the tree and not only what the tree looks like after.
import { recordingDocument } from '@aweftjs/testing';
const { document, ops } = recordingDocument();
mount(document.body, h('p', {}, 'hi'));
ops; // ['insert <p> into <body> before end']
ops.length = 0; // clear between the steps of a testOne line per insert, remove, replace, text write, attribute write and clear, in order. A node made elsewhere joins the recording when it is inserted.
The tier rule
Packages are numbered, and a package may import downward only. boundaries.json is the table, this package holds the check, and packages/testing/scripts/check-boundaries.ts runs it over the imports that actually exist.
import { checkGraph } from '@aweftjs/testing';
checkGraph([['core', 'codec']], table); // [] means the graph is legalRuntime code is what the rule governs. Tests, scripts and examples are outside it, because a suite importing this harness creates no dependency in anything a user installs, and the definition of done requires exactly that import. They are still printed on every run, so an exclusion that starts hiding something is visible rather than silent.
Testing a module in isolation
loadModule instantiates one module the way @aweftjs/modules would, with its dependencies replaced by whatever the test hands over, so a module's own tests do not need a directory, a document or the modules it depends on.
import { loadModule } from '@aweftjs/testing';
import * as Create from './modules/posts/Create.ts';
const { instance, stop } = await loadModule({
exports: Create,
imports: { 'auth/Session': { userOf: () => 'u_1' }, 'lib/Log': { log: () => {} } },
config: { maxLength: 10 }, // merged over the module's defaults, as an extension would be
props: { site: 'test' }, // what the application would pass the loader
});
assert.equal((instance as Post).make('a long title'), 'post:a long ti');
await stop(); // unloads it, calling the instance's stop if it has oneEvery name in the module's deps needs an entry in imports, keyed by the full dependency name; a missing one is refused by name before anything is instantiated. The module is loaded through the real loader, so imports reaches the factory keyed by the last segment of each name, exactly as it would in an application.
Testing a whole backend
loadModule above is one module with its dependencies stubbed. loadServer is the level above: a real server, real modules, and the gate the application actually uses, on a listener that opens nothing. It takes createServer's options without the listener, which is the one part a test cannot supply.
import { loadServer } from '@aweftjs/testing';
const server = await loadServer({
sources: [fromDirectory(modules), auth],
store: createStore({ driver: memoryDriver(), declare: { ...paths } }),
gate: 'auth/Gate',
});
const answer = await server.fetch('/api/session', { method: 'POST', body });
const page = await server.open({ headers: { cookie } });
await page.asks.ask('board/Mine');
await server.stop();A throwaway or a server the suite never stops keeps its own resources alive: a cluster is a child process and a running one holds the test process open. Stop them in an after.
fetch and open are the two seams a listener feeds, so everything a deployment does goes through the same code a deployment does it with. open answers a socket, a link and the call channel; it throws when the gate refuses the handshake, carrying the status the gate answered with, because a browser handed a refusal gets a failed connection and not a response to read.
Signing in is yours. The sequence is a POST to your battery's session route, the Set-Cookie off the answer, and open with that cookie. It is five lines and it is not here, because putting it here would tie this package to one battery's routes and one idea of what a session is. recipes/full-stack/tests/board.test.ts is those five lines.
The security suite
securityChecks({ gate }) is the suite a server passes rather than claims, the way a runner passes roomChecks(). Each case is one named obligation citing the ASVS 5.0 requirements it proves (docs/security.md has the table), run against a server you start, so an application that boots its own modules behind the same server proves the same things about itself.
import { loadServer, securityChecks } from '@aweftjs/testing';
for (const c of securityChecks({ gate: 'auth/Gate' })) {
test(`${c.requirements.join(' ')}: ${c.name}`, () => c.run((given) => loadServer({
...given,
sources: [fromDirectory(modules), auth, ...given.sources],
store: createStore({ driver: memoryDriver(), declare: { ...paths } }),
})));
}start is yours: it loads the probe modules the suite hands it beside your own, starts under the gate the suite names (yours, with one rule of its own composed on top), passes the handlers the suite gives it, and answers { fetch, open, server, stop }, which is what loadServer answers. The suite speaks the session routes by their documented shape, POST and DELETE /api/session as @aweftjs/auth answers them, and imports no battery; a target with no session battery fails the session cases at the first sign-up, naming that. Nothing is skipped.
The suite is append-only: a case is added for every hole ever found and none is removed. It runs over the harness, where fetch may be handed a full URL, which is how the suite says a request arrived over TLS. A target over a real port would adapt fetch and open to it, and none ships: the transport's own bounds are pinned by the listener's tests.
npm run security ties the suite to the table: every requirement the table gives to the stack names a case here or a test elsewhere that exists, and every case cites a requirement the table gives to the stack.
Two ends of one socket
socketPair() is what loadServer opens over, and it is exported because a suite that is about the transport itself wants one without a server.
const [near, far] = socketPair(); // near is the page's end, far the server's
const link = connect(fromWebSocket(near));What one sends the other hears on a microtask, never synchronously. A send before the socket is open or after it closed reaches nobody, as a real one refuses and drops. Closing either closes both and fires close at both; a second close does nothing. A listener that throws is recorded on thrown and the listeners after it still run, because one socket here carries both the link and the call channel and a throwing link would otherwise mean an ask that never settles.
An end also carries peer and fire. fire('open', {}) is how a suite driving a client's retry hands the page the event a browser would have fired. A listener added while an event is dispatching does not hear that event, which is what a real EventTarget does. Pass 0 for a near end that starts connecting, which is the order a page sees: the server is handed an accepted socket before the page is told about its own.
This is not sync's inProcess. That answers two channels, and a channel is one plane; a socket carries the link and the call channel together and has the readyState and the close event a retry reads.
A database that goes away
throwaway() on the /postgres subpath starts an empty cluster in a temporary directory on a free port, and stops it when you are done.
import { throwaway } from '@aweftjs/testing/postgres';
const db = await throwaway();
const store = createStore({ driver: postgresDriver(await db.pool()) });
// ...
await db.stop();Every pool() is in a schema of its own, so two checks in one file never see each other's tables, which is also how two applications share one database. The throwaway owns every pool it handed out and ends them all in stop, so do not end one yourself: pg throws "Called end on pool more than once" for the second call, and that throw is its, not this package's.
embedded-postgres and pg are optional peers: a project that never opens a store installs neither, and one that asks for a throwaway without them gets peer-not-installed naming what to install. throwaway takes how the peers are reached, so that refusal can be reached without uninstalling anything.
Reading the page a test drives
audit and walk on the /browser subpath take the page object a browser driver already handed the test and answer what a screen reader and a keyboard would find there (design 267). Neither throws on a finding; the test says what a finding means for it.
import { audit, walk } from '@aweftjs/testing/browser';
const { violations } = await audit(view);
assert.deepEqual(violations, [], violations.map((v) => `${v.rule}: ${v.help}`).join('\n'));
const { stops, problems } = await walk(view);
assert.deepEqual(problems, [], problems.map((p) => `${p.reason} at ${p.target}: ${p.fix}`).join('\n'));audit puts axe-core into the page and runs it over the document, or over options.root, with the WCAG 2.x A and AA tags (options.tags replaces the list). A violation carries axe's rule id, its wcag* tags as axe writes them (wcag2aa, wcag143), its help sentence and link, and the nodes as a selector and the markup. Run it once per colour scheme: the page's colours are what it measures, and a second run on the same page reuses the script.
walk presses Tab from the top of the page until the focus comes back round, or options.limit (300) presses have gone by, and answers every stop (tag, id, role, name, and whether it draws a ring while it matches :focus-visible) and the problems: focus-not-visible for a stop with no outline and no box-shadow, focus-stuck when a press left the focus where it was, focus-loops when a press sent the focus back round the page without letting it leave, unreachable for a focusable element the walk never landed on, never-cycles when the limit ran out. Whatever the page had focused loses it first. A ring is the element's own outline or box-shadow, or one an ancestor draws for the control inside it through a rule about focus, and nothing else, so a page that shows its focus by changing a border colour is reported as ringless. A radio group is one stop, an element with no box (inside a closed details, a closed dialog, a hidden parent) is not expected, and a frame, a shadow host or a media element's own controls is one stop the focus moves inside without the walk reading where. Run it with no modal dialog open: the page behind one is inert and the walk reports it unreachable.
The page is structural: evaluate(source), addScriptTag({ content }) and keyboard.press(key), which Playwright's Page satisfies (the suite passes one), and this package imports no browser driver. axe-core is an optional peer: a project that never audits installs nothing, and one that asks without it gets axe-not-installed. options.locate says where the script is when the installed one is not the one to use, and is how that refusal is reached without uninstalling anything.
What neither reads is the criteria that need a person: meaning carried by colour alone, the order of the reading, a heading that describes its section, a time limit, an error message that says what to do. The build refuses what the source settles (@aweftjs/build, The access rules) and the mount throws on a nameless Button and a page with no lang or title (@aweftjs/ui); these two read the rendered page for the rest.
Letting scheduled work run
settle() yields to the timer queue ten times, so work that schedules more work gets to run. A single await drains microtasks and nothing else, which is why a suite that awaits once and asserts sees a tree that is half settled. Pass a whole number of one or more for a different count; anything else is refused rather than settling for nothing and failing an assertion further on.
Seeded randomness
A property test is worth having only if a failure can be run again, so a failing assertion prints its seed and that seed reproduces the run exactly.
import { randomBelow, randomFrom } from '@aweftjs/testing';
const random = randomFrom(20260901);
const victim = items[randomBelow(random, items.length)];There is one generator here rather than one per suite. Copies of the same shift register drifted apart in small ways, and a seed that reproduces a failure under one copy reproduces nothing under another.
The words check
npm run words reads every tracked file for the vocabulary of how the stack was built (who decided a thing, when, through which review) and prints each line that carries a word from the list, with what to write instead. The list is in packages/testing/src/words.ts, one entry per word with its fix. It reads text, not syntax, so it matches the spellings the process used and leaves the words the code needs alone.
import { checkWords } from '@aweftjs/testing';
checkWords([{ path: 'note.md', text }]); // [] means the text is cleanWith paths, node packages/testing/scripts/check-words.ts docs/design reads those files instead of the whole tree.
What a suite has to name
npm run exercised reads each package's surface.txt and errors.txt beside its tests and fails a covered package whose own suite never names one of its value exports, or never quotes one of its refusal reasons (design 285). Both files are generated: surface.txt by npm run surface, one export per line as value name: type with a subpath's lines prefixed by the subpath, and errors.txt by npm run errors, one reason: fix per line. A test counts when it is not the surface list and not a white-box internal.* file; a name in a comment does not count, and a reason as the whole of a regular expression does, since a refusal's message opens with its reason. The covered packages are listed in the script and the list only grows; the rest have their counts printed. testing is exempt, because its surface is the suites of the other packages.
import { checkExercised } from '@aweftjs/testing';
checkExercised('store', surface, errors, tests); // [] means the suite names everythingThe operator sweep
npm run sweep -- <package> copies the repo into a scratch directory, flips one operator at a time in that package's sources (a comparison, a logical operator, a boolean a function returns), runs the package's own suite against each flip, and prints every flip the suite let through with its file, line and operator (design 287). It runs on request and never in the gate: one flip is one run of the suite, two test files at a time so the machine stays usable, and --only=<file>[,<file>] narrows it to the files you are working on. A survivor is either killed by a test or written down as equivalent with the reason, wherever the change that ran the sweep is written up.
import { flipSite, sweepSites } from '@aweftjs/testing';
const [first] = sweepSites(source); // where the file can be flipped, in order
const flipped = flipSite(source, first!); // the same text with that one operator changedRunning the gate
npm test at the root is the whole gate: typecheck, the dependency rules, the tier rule over real imports, every package's suite with its coverage threshold, and every proof program. packages/testing/scripts/run-tests.ts is the part that runs the suites, and generate-fixtures.ts rewrites spec/fixtures/ from the generator entries.
API
Every export of @aweftjs/testing, its signature as the compiler resolves it, and its block comment.
@aweftjs/testing
AdapterCheck
interface AdapterCheck { readonly name: string; run(make: MakeAdapter): Promise<void>; }One named obligation an adapter has to meet.
Applier
type Applier = (initial: DocumentJson, commits: readonly Commit[]) => DocumentJson;One reading of the format, as a function.
The suite is the same for every implementation, and there is more than one: a model that stores plain data, and a real reactive tree. Both must reach the same document from the same bytes, which is the point of running the fixtures twice.
Params
initial: the document to start fromcommits: the commits to apply, in order, each whole
Returns the document reached. Throws with a stated reason when a commit is refused.
CommitJson
interface CommitJson { readonly bytes: string; readonly deltas: readonly DeltaJson[]; readonly tag?: string; }A commit as plain JSON: its bytes in hex, the deltas they carry, and an optional tag.
Connected
interface Connected { readonly socket: PairedSocket; readonly link: Link; readonly asks: Requests; readonly request: Request; }What open answers when the gate let the handshake through.
DeltaJson
interface DeltaJson { readonly type: DeltaType; readonly id: string; readonly ref: RefJson; readonly value?: ValueJson; }A delta as plain JSON, with value absent exactly when the type is remove.
DocumentJson
interface DocumentJson { readonly root: string; readonly observables: Readonly<Record<string, ObservableJson>>; }A whole document as plain JSON.
Flat, keyed by id in text form, with the root named separately. Flat rather than nested because an observable can be named from more than one place, and a nested spelling would have to pick one of them and quietly lose the others.
DriverCheck
interface DriverCheck { readonly name: string; run(make: MakeDriver): Promise<void>; }One named obligation a driver has to meet.
FileAdapter
interface FileAdapter { readonly name: string; put(key: string, stream: ReadableStream<Uint8Array>, options: { readonly type: string; readonly size: number; }): Promise<void>; open(key: string): Promise<ReadableStream<Uint8Array> | undefined>; head(key: string): Promise<{ readonly size: number; } | undefined>; remove(key: string): Promise<void>; }The part of an uploads adapter this suite exercises.
Stated structurally rather than imported, so testing does not depend on uploads: the package it checks depends on it in turn.
Fixture
interface Fixture { readonly name: string; readonly description: string; readonly initial: DocumentJson; readonly commits: readonly CommitJson[]; readonly final: DocumentJson; }One conformance case: bytes, what they mean, and the document they produce.
This is the contract an implementation in another language reads. initial is the document before, commits are applied in order, final is the document after, and every one of them is stated as plain JSON so that nothing about this repo's types is needed to consume it.
initial and final are written by hand rather than generated, which is the point: an ending document produced by the implementation under test would only say the implementation agrees with itself.
InvalidFixture
interface InvalidFixture { readonly name: string; readonly description: string; readonly stage: 'decode' | 'apply'; readonly reason: string; readonly bytes: string; readonly initial?: DocumentJson; }One case that must be refused, and the reason it must be refused for.
reason is the contract, not the message. Two implementations that both reject an input for different stated reasons have not agreed on the format, they have agreed on rejecting one string. stage says where the refusal is due: decode for bytes that are not a commit, apply for a commit that is well formed and cannot be applied, which is the only case that needs initial.
ListenerCheck
interface ListenerCheck { readonly name: string; run(make: MakeListener): Promise<void>; }One named obligation a listener has to meet.
LoadedModule
interface LoadedModule { readonly instance: unknown; stop(): Promise<void>; }The instance the module produced, and the way to unload it.
LoadedServer
interface LoadedServer { readonly server: Server; fetch(path: string, init?: RequestInit): Promise<Response>; open(options?: OpenOptions): Promise<Connected>; stop(): Promise<void>; }A running server with no port, and the two seams a listener would feed it.
MadeListener
interface MadeListener { readonly listener: Listener; url(): string; }No block comment on this export.
MakeAdapter
type MakeAdapter = () => Promise<FileAdapter> | FileAdapter;What a check needs: an adapter nobody else is using.
MakeDriver
type MakeDriver = () => Promise<StoreDriver> | StoreDriver;What a check needs: a driver nobody else is using, and a way to be rid of it.
MakeListener
type MakeListener = () => Promise<MadeListener> | MadeListener;What a check needs: a listener nobody else is using, and the URL to reach it at once it has started.
MakeRunner
type MakeRunner = () => Runner;What a check needs: a fresh runner nobody else is using.
Manifest
interface Manifest { readonly name: string; readonly dependencies?: Readonly<Record<string, string>> | undefined; readonly devDependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependenciesMeta?: Readonly<Record<string, { readonly optional?: boolean; }>> | undefined; }The dependency-bearing part of one package.json.
ModuleUnderTest
interface ModuleUnderTest { readonly exports: ModuleExports; readonly imports?: Readonly<Record<string, unknown>>; readonly config?: Readonly<Record<string, unknown>>; readonly props?: Readonly<Record<string, unknown>>; }No block comment on this export.
ObservableJson
interface ObservableJson { readonly kind: ObservableKind; readonly slots: Readonly<Record<string, ValueJson>>; }One observable as plain JSON: its kind, and its slots by key.
OpenOptions
interface OpenOptions { readonly headers?: Record<string, string> | undefined; readonly url?: string | undefined; readonly peer?: Peer | undefined; }What open may be told about the handshake it is about to make.
Opened
interface Opened { readonly socket: { readonly readyState: number; send(data: string | Uint8Array): void; close(): void; }; readonly link: Link; readonly asks: Requests; }A connection the target opened: the socket, the link and the call channel over it.
PackageInfo
interface PackageInfo { readonly tier: number | 'integrator'; readonly plane: Plane; readonly imports: readonly string[] | '*'; }A package's place in the table: how high it sits, which side it runs on, what it reaches.
PairedSocket
interface PairedSocket extends SocketLike { peer: PairedSocket | undefined; readonly sent: (Uint8Array | string)[]; readonly thrown: unknown[]; fire(type: string, event: { data?: unknown; }): void; }One end of a pair: a socket, plus what it was asked to send.
Plane
type Plane = 'client' | 'server' | 'isomorphic';Which side a package runs on. isomorphic may be imported from either.
PublishManifest
interface PublishManifest { readonly name: string; readonly version?: string | undefined; readonly private?: boolean | undefined; readonly engines?: Readonly<{ node?: string | undefined; }> | undefined; readonly files?: readonly string[] | undefined; readonly exports?: Readonly<Record<string, string | Readonly<Record<string, string>>>> | undefined; readonly dependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependencies?: Readonly<Record<string, string>> | undefined; readonly publishConfig?: Readonly<{ access?: string | undefined; }> | undefined; readonly scripts?: Readonly<Record<string, string>> | undefined; }The publishing-relevant part of one package.json.
Recording
interface Recording { readonly document: DocumentLike & { readonly body: ElementLike; readonly head: ElementLike; }; readonly ops: string[]; }No block comment on this export.
RefJson
interface RefJson { readonly kind: ObservableKind; readonly key: string; }A ref as plain JSON, with the key always a string so a fixture stays readable.
An object key is itself, an array position is hex, and a map key is the id in its textual form: sixteen base64url characters, as idToText writes it, not hex.
RoomCheck
interface RoomCheck { readonly name: string; run(make: MakeRunner): Promise<void>; }One named obligation the window has to meet behind a runner.
SecurityCase
interface SecurityCase { readonly name: string; readonly requirements: readonly string[]; }One case of the suite, as much of it as the table needs.
SecurityCheck
interface SecurityCheck { readonly name: string; readonly requirements: readonly string[]; run(start: StartTarget): Promise<void>; }One named obligation a server has to meet, and the requirements it proves.
SecurityRow
interface SecurityRow { readonly id: string; readonly level: string; readonly owner: string; readonly check: string; }One line of the table.
SecurityTarget
interface SecurityTarget { fetch(path: string, init?: RequestInit): Promise<Response>; open(options?: { readonly headers?: Record<string, string> | undefined; readonly url?: string | undefined; }): Promise<Opened>; readonly server: Server; stop(): Promise<void>; }A started application the suite runs against. loadServer answers this shape as it is.
Site
interface Site { readonly line: number; readonly column: number; readonly from: string; readonly to: string; }One place a source file can be flipped.
StartOptions
interface StartOptions { readonly sources: readonly Source[]; readonly gate: Gate | string; readonly handlers: { failed(name: string, error: unknown): void; }; }What the suite hands start: its own probe modules, the gate the target must run under, and where the server reports a module that broke.
StartTarget
type StartTarget = (options: StartOptions) => Promise<SecurityTarget>;The caller's boot. It loads the sources given beside its own (a store declaring the auth paths, and a session battery answering the auth routes), starts under the gate given, and hands the server the handlers given, so a module the suite breaks on purpose is reported to the suite rather than raised where nothing catches it.
StoreDriver
interface StoreDriver { write(write: { doc: string; root: string; rootKind: string; rows: readonly { id: string; kind: string; edge?: { parent: string; slot: string; } | null; set: Record<string, unknown>; unset: readonly string[]; }[]; dropped: readonly string[]; body: Uint8Array; project?: Record<string, unknown>; }): Promise<number>; declare(declaration: Readonly<Record<string, readonly string[]>>): Promise<void>; find(lookup: { where: { field: string; op: string; value: unknown; }; sort?: { field: string; direction: 'asc' | 'desc'; }; limit?: number; after?: string; }): Promise<{ doc: string; fields: Record<string, unknown>; cursor: string; }[]>; scan(limit: number, after?: string): Promise<{ doc: string; fields: Record<string, unknown>; cursor: string; }[]>; create(doc: string, root: string, rootKind: string): Promise<boolean>; read(doc: string): Promise<{ root: string; rootKind: string; rows: unknown[]; } | null>; since(doc: string, seq: number): Promise<{ seq: number; body: Uint8Array; }[]>; head(doc: string): Promise<number>; truncate(doc: string, seq: number): Promise<void>; forget(doc: string, ids: readonly string[]): Promise<void>; remove(doc: string): Promise<void>; close(): Promise<void>; }The part of a store driver this suite exercises.
Stated structurally rather than imported, so testing does not depend on store: an integrator may know everything, but a suite that forced a dependency edge would make the package it checks unable to depend on it in turn.
TestExists
type TestExists = (file: string, text: string, kind: 'test' | 'doc') => boolean;Whether a file holds what a row names: a test of that title, or a document section under that heading.
TestSource
interface TestSource { readonly path: string; readonly text: string; }One test file of the package.
ThemeSource
interface ThemeSource { readonly path: string; readonly text: string; }One file to read.
ThemeViolation
interface ThemeViolation { readonly path: string; readonly line: number; readonly where: string; readonly property: string; readonly literal: string; readonly fix: string; }One value written where it stands rather than named.
ThrownRefusal
interface ThrownRefusal { readonly reason: string; readonly fix: string; }One refusal a package can throw: the token, and the remedy offered with it.
Named for what it is rather than Refusal, which core and schema already use for the different thing an application's own rule returns.
ValueJson
type ValueJson = null | boolean | number | string | { readonly bytes: string; } | { readonly ref: string; readonly kind: ObservableKind; readonly edge: EdgeKind; };A value as plain JSON: a primitive, { bytes } for a byte string, or a reference.
Violation
interface Violation { readonly from: string; readonly to: string; readonly rule: 'upward-tier' | 'cross-plane' | 'unknown-package' | 'not-allowed'; readonly detail: string; }One illegal import edge, and which rule it broke.
WordSource
interface WordSource { readonly path: string; readonly text: string; }One file to read.
WordViolation
interface WordViolation { readonly path: string; readonly line: number; readonly word: string; readonly fix: string; readonly text: string; }One line carrying a word from the list.
adapterChecks
adapterChecks: () => AdapterCheck[]The obligations of an adapter, as named checks.
Returns the checks. Run each with a fresh adapter.
Example
for (const c of adapterChecks()) test(c.name, () => c.run(() => directory(tmp)));applyCommit
applyCommit: (doc: DocumentJson, commit: Commit) => DocumentJsonApply one commit to a document.
Params
doc: the document beforecommit: the commit to apply, its deltas in any order
Returns a new document. The input is not modified.
Throws a CodecError naming the rule broken. Every delta is checked before any is applied, which is what makes a commit atomic: a commit that would break a rule leaves the document exactly as it was, so no observer ever sees the halfway state.
aweftPackageOf
aweftPackageOf: (specifier: string) => string | undefinedThe package a specifier reaches inside this stack, or undefined for anything else.
Params
specifier: a module specifier as written
Returns the package name for @aweftjs/<name> and any subpath under it. A subpath import is still an edge to that package; whether reaching a subpath is legal is the exports map's question, not this one's.
Example
aweftPackageOf('@aweftjs/dom/router'); // 'dom'canonicalJson
canonicalJson: (value: unknown) => stringA document as one string, with every key in a stated order.
Two documents are the same document when these strings match. Comparing the objects directly would make the order keys happen to be inserted in part of the answer, and applying the same commit in two orders inserts them in two orders.
checkEdge
checkEdge: (from: string, to: string, table: Readonly<Record<string, PackageInfo>>) => Violation | nullCheck one import edge against the boundary rules.
Params
table: the tier and plane map.boundaries.jsonat the repo root is the one copy
Returns a Violation, or null when the edge is allowed.
checkExercised
checkExercised: (name: string, surface: string, errors: string, tests: readonly TestSource[]) => string[]Every export and every reason the suite does not name.
Params
name: the package, for the reportsurface: the text of itssurface.txterrors: the text of itserrors.txt, empty when it refuses for no reasontests: its test files; the ones that count are chosen here, so hand in the directory
Returns one line per miss, naming the package and the export or reason, empty when the suite names them all. An export counts as named when it appears as a whole word outside a comment; a reason when it appears quoted, or as the whole of a regular expression, since a refusal's message opens with its reason and assert.throws(fn, /reason/) is the shortest way to ask.
Example
checkExercised('store', surface, errors, tests);
// ['store: projectionOf is exported and no test names it', "store: 'not-open' is a refusal and no test quotes it"]checkFixture
checkFixture: (f: Fixture, applier?: Applier) => voidRun one fixture.
Params
f: the fixture, already parsed
Throws an Error naming the fixture and the check that failed, including when the applier itself throws on a case that is valid. Returns nothing on success.
checkGraph
checkGraph: (edges: readonly (readonly [string, string])[], table: Readonly<Record<string, PackageInfo>>) => Violation[]Check a whole import graph.
Params
edges: every package-to-package import in the repotable: the tier and plane map
Returns every violation found, in input order. Empty means the graph is legal.
checkInvalidFixture
checkInvalidFixture: (f: InvalidFixture, applier?: Applier) => voidRun one rejection fixture.
Params
f: the fixture.stagesays whether the bytes must be refused when decoded, or decode cleanly and be refused when applied toinitial.
Throws an Error if the input was accepted, or refused for a different stated reason than the fixture names. Rejecting for the wrong reason counts as a failure: a format whose implementations disagree about why something is invalid has not been specified.
checkManifests
checkManifests: (manifests: readonly Manifest[], allowed: readonly string[], perPackage?: Readonly<Record<string, readonly string[]>>) => string[]Every dependency declaration the allowlist does not cover.
Params
manifests: the packages' manifestsallowed: dependency names permitted anywhere;@aweftjs/packages are always permitted. An entry ending/*covers a family, so@iconify-json/*is one entryperPackage: extra names permitted for specific packages, by package name, in the same spelling
Returns one line per violation, naming the package and the dependency, empty when clean. A third-party peer that is not marked optional is a violation even when the allowlist names it.
Example
const violations = checkManifests(manifests, ['typescript'], {});checkPublishing
checkPublishing: (manifests: readonly PublishManifest[]) => string[]Every way a set of manifests would publish something other than what design 256 describes.
Params
manifests: the packages' manifests, aspackage.jsonholds them
Returns one line per violation, naming the package and what is wrong, empty when clean. The version check compares the manifests against each other rather than against a constant, because the packages version in lockstep and no file is the record of which version that is.
Example
const violations = checkPublishing([JSON.parse(readFileSync('packages/core/package.json', 'utf8'))]);checkSecurityTable
checkSecurityTable: (rows: readonly SecurityRow[], cases: readonly SecurityCase[], testExists: TestExists) => string[]Every way the table and the suite could disagree.
Params
rows: the tablecases: the suite's cases, by name and the requirements each citestestExists: answers whethertest: <file>#<title>names a test that is there
Returns one line per problem, empty when the table holds: an id that is not Vn.n.n or appears twice; a level that is not 1 or 2; an owner that is not one of the four words; an empty check; a stack row whose check is not suite: <name> naming a case, test: <file>#<title> naming a test that exists, or doc: <file>#<heading> naming a section that exists; a case citing an id the table lacks or does not mark stack.
Example
const problems = checkSecurityTable(rows, securityChecks({ gate: open }), exists);checkTheme
checkTheme: (files: readonly ThemeSource[]) => ThemeViolation[]Every value written where it stands rather than named.
Params
files: the source files to read, each with the path to report it under
Returns one entry per literal, in file then position order. Empty means every value in a CSS position came through a $name.
Example
const found = checkTheme([{ path: 'a.tsx', text: readFileSync('a.tsx', 'utf8') }]);checkWords
checkWords: (sources: readonly WordSource[]) => WordViolation[]Reads every line of every source against the list and reports each line that carries a word.
Params
sources: the files, each with the path it will be reported under
Returns one violation per matching line and rule, in file order then line order. A line carrying two words from the list is reported twice. An empty array means the text is clean.
Example
checkWords([{ path: 'a.md', text: 'Decided by: the maintainer.' }]);
// [{ path: 'a.md', line: 1, word: 'a decided-by header', fix: 'drop the line', text: '...' }]commitToJson
commitToJson: (commit: Commit, bytes: Uint8Array<ArrayBufferLike>) => CommitJsonA commit and its bytes in the shape a fixture states them, for comparing against one.
deltaFromJson
deltaFromJson: (d: DeltaJson) => DeltaThe delta a JSON one names.
This is the direction that makes a fixture's bytes checkable against something beside the decoder: the stated JSON is ordered independently of the encoder's sort, so encoding it and comparing to the stated bytes asks a question the decoder's own output cannot answer.
deltaToJson
deltaToJson: (d: Delta) => DeltaJsonA delta in its JSON form, as a fixture states it.
driverChecks
driverChecks: () => DriverCheck[]Every obligation a store driver has.
Returns the checks, each of which takes a factory for a driver nobody else is using and throws on failure.
Example
for (const check of driverChecks()) test(check.name, () => check.run(() => memoryDriver()));errorLines
errorLines: (refusals: readonly ThrownRefusal[]) => string[]The refusals as the lines that go in errors.txt.
Params
refusals: whaterrorsOfreturned
Returns one line per refusal, reason then colon then the fix.
Example
const text = errorLines(refusals).join(String.fromCharCode(10));errorsOf
errorsOf: (files: readonly string[], program: Program) => ThrownRefusal[]Every refusal thrown in these files.
Params
files: absolute paths to the source files of one packageprogram: a program those files belong to
Returns one entry per distinct reason and fix, sorted by reason then fix. A reason thrown from several places with several remedies appears once per remedy, because that difference is exactly what a reader needs to see in a diff.
Throws not-in-program when a path is not part of the program handed in, reason-not-literal when a token is built at runtime, and fix-missing or fix-not-literal when the remedy is absent or built, which would make the index a description of code rather than of what a reader is told.
Example
const refusals = errorsOf(files, program);flipSite
flipSite: (text: string, site: Site) => stringThe source with one site flipped.
Params
text: the source the site was found insite: one of its sites
Returns the same text with that one operator replaced, every other byte unchanged.
Example
flipSite('if (a < b) go();', { line: 1, column: 5, from: ' < ', to: ' <= ' });
// 'if (a <= b) go();'idFromText
idFromText: (text: string) => Id (from @aweftjs/codec)The id behind its text form.
Params
text: sixteen base64url characters, as idToText writes them
Returns the 12 bytes, as an id.
Throws when the length or the alphabet is wrong. Round tripping through text is lossless, so anything that does not round trip was never one of these ids.
Example
const id = idFromText(slotKey);idToText
idToText: (id: Id) => string (from @aweftjs/codec)The textual form of an id: sixteen base64url characters.
Params
id: an id. Raw bytes reach one through assertId
Returns the text form, safe in a URL, a log line, or a JSON object key.
Throws a CodecError with reason invalid-id when the bytes are not ID_BYTES long.
listenerChecks
listenerChecks: () => ListenerCheck[]Every obligation a server listener has.
Returns the checks, each named, each taking a function that makes a fresh listener and says where to reach it.
Throws each check's run throws an assertion when the listener misses that obligation, and no-status-line when a raw upgrade request is closed with nothing written back.
Example
for (const c of listenerChecks()) test(c.name, () => c.run(() => {
const listener = node({ port: 0 });
return { listener, url: () => `http://127.0.0.1:${listener.port}` };
}));loadFixtures
loadFixtures: (dir: string | URL) => Fixture[]Every fixture in a directory, sorted by filename.
Params
dir: the fixtures directory, as a URL or an absolute path ending in a slash
Returns the parsed fixtures, in filename order.
Example
for (const f of loadFixtures(new URL('../../../spec/fixtures/', import.meta.url))) {
checkFixture(f);
}loadInvalidFixtures
loadInvalidFixtures: (dir: string | URL) => InvalidFixture[]Every rejection fixture in a directory, sorted by filename.
Params
dir: the invalid-fixtures directory, as a URL or an absolute path ending in a slash
Returns the parsed rejection fixtures, in filename order.
Example
for (const f of loadInvalidFixtures(new URL('../../../spec/fixtures/invalid/', import.meta.url))) {
checkInvalidFixture(f);
}loadModule
loadModule: (test: ModuleUnderTest) => Promise<LoadedModule>Instantiate one module the way the loader would, with its dependencies replaced by the instances the test supplies.
Params
test.exports: the moduletest.imports: one instance per dependency name inexports.depstest.config: merged overexports.defaultstest.props: spread into the factory's props
Returns the instance and a stop that unloads it.
Throws dependency-not-stubbed, naming the dependency, when one has no entry in imports: a test that forgot one should not get an undefined import.
Example
const { instance, stop } = await loadModule({
exports: await import('./modules/posts/Create.ts'),
imports: { 'auth/Session': { userOf: () => 'u_1' } },
config: { maxLength: 10 },
});loadServer
loadServer: (options: Omit<ServerOptions, "listener">) => Promise<LoadedServer>Boot a server with real modules on a listener with no port.
modelApplier
modelApplier: ApplierThe harness's own reading of the specification: plain data, no reactivity.
moduleSpecifiers
moduleSpecifiers: (source: string, fileName?: string) => string[]Every module specifier a source file names with a literal.
Params
source: the file's textfileName: used only for parser diagnostics
Returns the specifiers of static imports and re-exports, dynamic import() calls whose argument is a string or substitution-free template, and direct require() calls, in order of appearance. Comments and ordinary strings are not imports and are not here.
Example
const packages = moduleSpecifiers(text).map(aweftPackageOf).filter((p) => p !== undefined);parseSecurityTable
parseSecurityTable: (text: string) => SecurityRow[]The table, from the CSV text: a header line id,level,owner,check, then one row per line. The check column may carry commas; the first three commas split the row.
Params
text: the file's text
Returns the rows, in file order. A blank line is skipped.
Example
parseSecurityTable(readFileSync('docs/security/asvs.csv', 'utf8'));randomBelow
randomBelow: (random: () => number, bound: number) => numberA whole number in [0, bound).
Params
random: a stream from randomFrombound: how many values, at least 1
Returns an index into something of that length.
Throws invalid-bound when the bound is under 1.
Example
const victim = items[randomBelow(random, items.length)];randomFrom
randomFrom: (seed: number) => () => numberA seeded stream of numbers in [0, 1).
Params
seed: any integer. It is folded to 32 bits, so seeds equal modulo 2^32 give one stream, and zero is mapped to 1, since a shift register cannot leave zero
Returns a function giving the next number. Two generators made with one seed produce the same stream, so a failure prints its seed and the run can be repeated exactly.
Example
const random = randomFrom(20260901);
assert.ok(check(list), `failed at seed 20260901`);recordingDocument
recordingDocument: () => RecordingA light document that records what is done to it.
Returns the document and its ops, one line per operation: insert <li> into <ul> before end, remove <li> from <ul>, text "a" -> "b", attr class="x" on <div>, unattr class on <div>, clear <ul>. Nodes made through the document's factories are recorded, and so is anything cloned from one; nodes from elsewhere are not.
Example
const { document, ops } = recordingDocument();
mount(document.body, h('p', {}, 'hi'));
assert.deepEqual(ops, ['insert "hi" into <p> before end', 'insert <p> into <body> before end']);refFromJson
refFromJson: (r: RefJson) => RefThe ref a JSON one names. The inverse of refToJson, and the fixtures rely on it round tripping.
refToJson
refToJson: (ref: Ref) => RefJsonA ref in its JSON form, and back. The pair round trips, which the fixtures depend on.
roomChecks
roomChecks: () => RoomCheck[]The window half of the escape suite: what a hostile module cannot do from inside a room, whatever runner made the room.
Returns the checks, each named, each taking a function that makes a fresh runner.
Throws each check's run throws room-check-failed when the runner misses that obligation, and the detail says what was expected.
Example
for (const c of roomChecks()) test(c.name, () => c.run(() => inProcess()));securityChecks
securityChecks: (options: { readonly gate: string | Gate<unknown>; }) => SecurityCheck[]Every security obligation a server has, each citing the ASVS 5.0 requirements it proves.
Params
options.gate: the gate the target runs under, aGateor the name of a module that is one; the suite composes one rule of its own on top and starts the target under that
Returns the checks, each named, each taking start: the caller's boot, which loads the sources given beside its own store and session battery, starts under the gate given with the handlers given, and answers { fetch, open, server, stop }. loadServer answers that shape, so over the harness a case is one line.
Throws each check's run throws an assertion naming what the target did instead. A target with no session battery fails the session cases at the first sign-up, naming that.
Example
for (const c of securityChecks({ gate: 'auth/Gate' })) {
test(`${c.requirements.join(' ')}: ${c.name}`, () => c.run((given) => loadServer({
...given, sources: [auth, ...given.sources], store: newStore(),
})));
}seedFrom
seedFrom: (text: string) => numberA seed derived from the fixture name, so a failing shuffle is the same one next run.
settle
settle: (rounds?: number) => Promise<void>Yield to the timer queue, once per round, so work that schedules more work gets to run.
A single await drains microtasks and nothing else, which is why a suite that awaits once and asserts sees a tree that is half settled. Ten rounds is what the suites that wrote this by hand all chose.
shuffle
shuffle: <T>(items: readonly T[], seed: number) => T[]Reorder deterministically, from a seed.
Params
items: what to reorderseed: the same seed always gives the same order
Returns a new array. Used to check that the order deltas arrive in changes nothing, so it has to be repeatable: a failure that cannot be run again is a rumour. A shuffle can legitimately come back in the order it went in, so it is never the only reordering a check tries.
slotKeyOf
slotKeyOf: (ref: Ref) => string (from @aweftjs/codec)The name a slot has in a path, and the key a document files it under.
Params
ref: the slot a delta names
Returns an object key as itself, an array position in hex, a map identity in text form.
Throws a CodecError with reason invalid-id when a map slot's key is not an id. This is one mapping with one implementation, because a second copy is a second chance to disagree about what a document's own keys are.
Example
slotKeyOf({ kind: 'object', key: 'title' }); // 'title'socketPair
socketPair: (readyState?: number) => [PairedSocket, PairedSocket]Two ends of one socket with no port: what one sends, the other hears on a microtask, and closing either closes both.
surfaceOf
surfaceOf: (indexPath: string, program?: Program) => string[]The public surface of one package, as text.
Params
indexPath: absolute path to the package'ssrc/index.tsprogram: a program that already contains that file. One is built for it when omitted
Returns one line per export, sorted by name. A value reads value name: type, an interface or type alias reads type name: declaration, and an export re-exported from another package in the stack ends with ` (from
surfaceProgram
surfaceProgram: (indexPaths: readonly string[]) => ProgramOne program over several entry files, so a whole repo's surfaces share one checker.
Params
indexPaths: absolute paths to thesrc/index.tsof each package
Returns a program those paths can be read from. Building one per package costs a fresh parse of every file they share.
Example
const program = surfaceProgram(['/repo/packages/core/src/index.ts']);sweepSites
sweepSites: (text: string) => Site[]Every site in a source text, in file order.
Params
text: the source
Returns the sites, one per operator occurrence per line, in line then column order. A line that is or carries a comment has none, and an occurrence inside a string is left out. The operators are matched with their spaces, so <= is one site and never also a < one.
Example
sweepSites('if (a < b && c) return true;');
// [{ line: 1, column: 5, from: ' < ', to: ' <= ' }, { line: 1, column: 9, from: ' && ', to: ' || ' }, ...]themeTokens
themeTokens: (files: readonly ThemeSource[]) => string[]Every $name these files define.
Params
files: the source files to read
Returns the names, sorted, without their $, each once. Every scale step, every role, every size, every duration and every theme function the package ships.
Example
const names = themeTokens([{ path: 'roles.ts', text }]);valueFromJson
valueFromJson: (v: ValueJson) => ValueThe value a JSON one names. The inverse of valueToJson.
valueToJson
valueToJson: (v: Value) => ValueJsonA value in its JSON form, and back. Both directions are used, so both are exercised.
wordRules
wordRules: () => readonly { readonly word: string; readonly fix: string; }[]The names in the list, so a report can say what is checked without repeating the patterns.
@aweftjs/testing/browser
AuditNode
interface AuditNode { readonly target: string; readonly html: string; }One node axe found a violation on.
AuditOptions
interface AuditOptions { readonly root?: string; readonly tags?: readonly string[]; readonly locate?: () => string; }What audit takes beside the page. All three are optional.
AuditResult
interface AuditResult { readonly violations: readonly AuditViolation[]; readonly passes: number; }What audit answers: the rules the page broke, and how many it kept.
AuditViolation
interface AuditViolation { readonly rule: string; readonly impact: string | null; readonly wcag: readonly string[]; readonly help: string; readonly helpUrl: string; readonly nodes: readonly AuditNode[]; }One rule the page broke.
PageLike
interface PageLike { evaluate(source: string): Promise<unknown>; addScriptTag(options: { readonly content: string; }): Promise<unknown>; readonly keyboard: { press(key: string): Promise<void>; }; }The page object a browser driver hands a test, as much of it as these checks read.
Playwright's Page has these three, and the suite passes one: evaluate takes an expression as source text and answers what it evaluates to, addScriptTag puts a script into the page by its content, and keyboard.press presses one key.
Stop
interface Stop { readonly tag: string; readonly id: string | null; readonly role: string | null; readonly name: string; readonly ring: boolean; }Where the focus landed after one press.
WalkOptions
interface WalkOptions { readonly limit?: number; }What walk takes beside the page.
WalkProblem
interface WalkProblem { readonly reason: 'focus-not-visible' | 'focus-stuck' | 'focus-loops' | 'unreachable' | 'never-cycles'; readonly fix: string; readonly target: string; }One thing the walk found wrong, with the element it is about.
WalkResult
interface WalkResult { readonly stops: readonly Stop[]; readonly problems: readonly WalkProblem[]; }What walk answers: where the focus went, in order, and what was wrong on the way.
audit
audit: (page: PageLike, options?: AuditOptions) => Promise<AuditResult>Run axe-core over the page and answer what it found.
Params
page: the page a browser driver openedoptions:root, a selector for the part to audit;tags, the axe tags to run;locate, where the axe-core script is, when the installed one is not the one to use
Returns the violations, each with axe's rule id, its WCAG tags, its help and the nodes, and the count of rules that passed. Nothing is thrown for a violation; the test decides.
Throws axe-not-installed when locate, or the resolver in its place, finds nothing.
Example
const { violations } = await audit(view);
assert.deepEqual(violations, [], violations.map((v) => `${v.rule}: ${v.help}`).join('\n'));walk
walk: (page: PageLike, options?: WalkOptions) => Promise<WalkResult>Press Tab through the page, from the top, and answer where the focus went and what was wrong on the way. Whatever had the focus loses it first, so the walk starts before the first control.
Params
page: the page a browser driver openedoptions:limit, presses before the walk gives up
Returns every stop in order, and the problems: a stop with no visible ring, a press that moved nothing, a press that sent the focus back round the page without leaving it, a focusable element the walk never reached, or a walk that never came back round. Nothing is thrown for a problem; the test decides.
Example
const { stops, problems } = await walk(view);
assert.deepEqual(problems, [], problems.map((p) => `${p.reason} at ${p.target}: ${p.fix}`).join('\n'));@aweftjs/testing/postgres
Load
type Load = (name: 'embedded-postgres' | 'pg') => Promise<unknown>;How the optional peers are reached, so a test can reach the refusal without uninstalling one.
PoolLike
interface PoolLike { query(text: string, values?: readonly unknown[]): Promise<{ rows: Record<string, unknown>[]; }>; connect(): Promise<{ query(text: string, values?: readonly unknown[]): Promise<{ rows: unknown[]; rowCount: number | null; }>; release(): void; }>; end(): Promise<void>; }The minimum of pg.Pool this module hands back, typed here so pg stays an optional peer.
It carries connect as well as query because the pool is handed straight to postgresDriver, which takes connections rather than queries; a pool that could only answer query would make the one thing this module is for fail to compile.
Throwaway
interface Throwaway { readonly port: number; pool(): Promise<PoolLike>; stop(): Promise<void>; }A Postgres that exists for one run: the cluster's port, pools in schemas of their own, and the teardown that ends all of them.
throwaway
throwaway: (load?: Load) => Promise<Throwaway>Start a Postgres for this run: an empty cluster in a temporary directory on a free port.
Every pool it answers is in a schema of its own, which is how two checks in one file stay apart and how two applications share one database.
Refusals
Every error this package raises carries a reason to switch on and a fix that says what to do. These are its reasons, from errors.txt.
| reason | fix |
|---|---|
axe-not-installed | Install axe-core as a devDependency, or say where the script is with options.locate. |
dependency-not-stubbed | Add an entry under imports for every name the module lists in deps. |
fix-missing | Give the refusal a third argument saying what to do about it. |
fix-not-literal | Write the fix as a plain string; move anything variable into the detail. |
fixture-accepted | Refuse this input, with the reason the fixture names. |
fixture-mismatch | Make the implementation agree with the fixture, or regenerate with npm run fixtures. |
fixture-refused | Accept this case: the fixture is part of the format, so refusing it is the bug. |
handshake-refused | Open with the headers the gate needs, or assert the refusal with assert.rejects. |
invalid-bound | Check the collection is not empty before asking for an index into it. |
kind-conflict | State one kind for this id in every delta of the commit. |
missing-initial | Give the fixture an initial document, or set its stage to decode. |
multiple-attach | Remove the other attach edge in the same commit, or point at it with a reference. |
no-status-line | Answer every upgrade request with an HTTP status line, refusals included. |
not-in-program | Add the file to the program before reading it. |
not-in-program | Pass this path to surfaceProgram too, or let surfaceOf build its own program. |
peer-not-installed | Install embedded-postgres and pg as devDependencies. |
reason-not-literal | Write the reason as a plain string; a token built at runtime cannot be branched on. |
room-check-failed | Fix the runner so it meets this obligation, then run the checks again. |
rounds-not-positive | Pass a whole number of rounds, one or more, or nothing for the default. |
server-stopped | Call loadServer again; a stopped server is not restarted. |
slot-exists | Send a replace delta to overwrite the slot, or add under a free key. |
slot-missing | Send an add delta first, so the slot exists before it is replaced or removed. |
unreachable | Attach it under the root in the same commit that writes into it. |
wrong-reason | Throw the reason the fixture names, so every implementation agrees on why. |
Recipes
The programs in the stack's gate that use this package, each a job someone would have.
recipes/accessible-page: A page everyone can use, driven by keyboard and audited in both modes, and each guardrail catching one page written wrong: the build refusing an element no one can read, the mount throwing on a nameless button and a page with no language,auditandwalkreading what only the rendered page shows