✅ Assert README example results
Assign every documented matcher result to a variable and assert it with `assert.equal`, so the compiled README doc-tests verify behavior instead of merely running the code. The examples import `node:assert` themselves to stay copy-pasteable; the generator now merges that import into its prelude assert rather than rejecting it (only `node:test` remains reserved).
This commit is contained in:
1 parent
30d97a9209
commit
d0dd8641f2
3 files changed
+35
-13
No files matched your search
@@ -47,6 +47,7 @@ builder. Calling the builder with a handler map keyed by `T`'s members returns a
|
|||||||
matcher: a function from `T` to the common return type.
|
matcher: a function from `T` to the common return type.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
import { strict as assert } from "node:assert";
|
||||||
import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";
|
import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";
|
||||||
|
|
||||||
const matchAnswer = getPrimitiveUnionMatcher<"yes" | "no">();
|
const matchAnswer = getPrimitiveUnionMatcher<"yes" | "no">();
|
||||||
@@ -56,13 +57,15 @@ const reply = matchAnswer({
|
|||||||
no: () => "declined",
|
no: () => "declined",
|
||||||
});
|
});
|
||||||
|
|
||||||
reply("yes"); // "agreed"
|
const answer = reply("yes");
|
||||||
|
assert.equal(answer, "agreed");
|
||||||
```
|
```
|
||||||
|
|
||||||
Add a fallback as the second argument to leave members unhandled; the fallback
|
Add a fallback as the second argument to leave members unhandled; the fallback
|
||||||
receives the remainder:
|
receives the remainder:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
import { strict as assert } from "node:assert";
|
||||||
import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";
|
import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";
|
||||||
|
|
||||||
const matchLabel = getPrimitiveUnionMatcher<"yes" | "no" | "maybe">();
|
const matchLabel = getPrimitiveUnionMatcher<"yes" | "no" | "maybe">();
|
||||||
@@ -71,6 +74,12 @@ const label = matchLabel(
|
|||||||
{ yes: () => "agreed", no: () => "declined" },
|
{ yes: () => "agreed", no: () => "declined" },
|
||||||
(other) => `not sure: ${other}`, // other: "maybe"
|
(other) => `not sure: ${other}`, // other: "maybe"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const answer = label("yes");
|
||||||
|
assert.equal(answer, "agreed");
|
||||||
|
|
||||||
|
const fallback = label("maybe");
|
||||||
|
assert.equal(fallback, "not sure: maybe");
|
||||||
```
|
```
|
||||||
|
|
||||||
`getPrimitiveUnionMatcherW` is the same builder, but the matcher's return type
|
`getPrimitiveUnionMatcherW` is the same builder, but the matcher's return type
|
||||||
@@ -83,6 +92,7 @@ function takes the discriminant property's name and returns the handler-map
|
|||||||
builder, keyed by that property's tags.
|
builder, keyed by that property's tags.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
import { strict as assert } from "node:assert";
|
||||||
import { getTaggedUnionMatcher } from "tiny-pattern-ts";
|
import { getTaggedUnionMatcher } from "tiny-pattern-ts";
|
||||||
|
|
||||||
type Shape =
|
type Shape =
|
||||||
@@ -95,7 +105,11 @@ const area = matchShape({
|
|||||||
square: (s) => s.side ** 2,
|
square: (s) => s.side ** 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
area({ kind: "circle", radius: 2 });
|
const circleArea = area({ kind: "circle", radius: 2 });
|
||||||
|
assert.equal(circleArea, Math.PI * 4);
|
||||||
|
|
||||||
|
const squareArea = area({ kind: "square", side: 3 });
|
||||||
|
assert.equal(squareArea, 9);
|
||||||
```
|
```
|
||||||
|
|
||||||
`getTaggedUnionMatcherW` is the widening counterpart, exactly as in the
|
`getTaggedUnionMatcherW` is the widening counterpart, exactly as in the
|
||||||
|
|||||||
+3
-2
@@ -38,8 +38,9 @@ Compile every `ts / `typescript fence in the prose docs into a real
|
|||||||
`docs/` + `examples/` list is the natural extension; `development/` must never
|
`docs/` + `examples/` list is the natural extension; `development/` must never
|
||||||
be scanned (its fences are illustrative, not compilable).
|
be scanned (its fences are illustrative, not compilable).
|
||||||
- The generator hoists and merges leading imports, rewrites `tiny-pattern-ts` to
|
- The generator hoists and merges leading imports, rewrites `tiny-pattern-ts` to
|
||||||
the `#test-tiny-pattern-ts` source alias, and rejects an example that imports
|
the `#test-tiny-pattern-ts` source alias, merges an example's `node:assert`
|
||||||
`node:assert` / `node:test` (the prelude already binds both). Titles are the
|
import into the prelude assert, and rejects an example that imports
|
||||||
|
`node:test` (the prelude binds `test`). Titles are the
|
||||||
immediately preceding paragraph; a fence with no such paragraph is a fatal
|
immediately preceding paragraph; a fence with no such paragraph is a fatal
|
||||||
error, which keeps every example described.
|
error, which keeps every example described.
|
||||||
- `oxlint src/doc-test` reports "No files found" because the generated
|
- `oxlint src/doc-test` reports "No files found" because the generated
|
||||||
|
|||||||
@@ -46,18 +46,14 @@ const OUTPUT_DIR = "src/doc-test/__generated__";
|
|||||||
const TYPESCRIPT_LANGS: ReadonlySet<string> = new Set(["ts", "typescript"]);
|
const TYPESCRIPT_LANGS: ReadonlySet<string> = new Set(["ts", "typescript"]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modules the generated file already imports. An example that imports one of
|
* Modules the generated file already binds. An example that imports `node:test`
|
||||||
* these would collide with the prelude binding (duplicate `test` / `assert`), so
|
* would collide with the prelude `test` binding, so it is surfaced as a fatal
|
||||||
* it is surfaced as a fatal error and the example is rewritten.
|
* error. `node:assert` is allowed: it is merged into the prelude assert import.
|
||||||
*/
|
*/
|
||||||
const PRELUDE_MODULES: ReadonlySet<string> = new Set([
|
const PRELUDE_MODULES: ReadonlySet<string> = new Set(["node:test"]);
|
||||||
"node:assert",
|
|
||||||
"node:test",
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** One line of the prelude every generated file starts with. */
|
/** One line of the prelude every generated file starts with. */
|
||||||
const PRELUDE_TEST = 'import { test } from "node:test";';
|
const PRELUDE_TEST = 'import { test } from "node:test";';
|
||||||
const PRELUDE_ASSERT = 'import { strict as assert } from "node:assert";';
|
|
||||||
|
|
||||||
/** Indentation applied to every fence body line inside the `test` callback. */
|
/** Indentation applied to every fence body line inside the `test` callback. */
|
||||||
const INDENT = " ";
|
const INDENT = " ";
|
||||||
@@ -325,6 +321,17 @@ const handleNode = (state: BuilderState, name: string, node: Block): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guarantee `assert` is in scope in every generated file. The import is merged
|
||||||
|
* into any `node:assert` import an example already declares, so an example can
|
||||||
|
* stay self-contained without colliding with the harness.
|
||||||
|
*/
|
||||||
|
const ensurePreludeAssert = (named: Map<string, NamedImportGroup>): void => {
|
||||||
|
addNamedImport(named, { source: "node:assert", isType: false }, [
|
||||||
|
"strict as assert",
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
/** Walk one Markdown file and collect its cases and hoisted imports. */
|
/** Walk one Markdown file and collect its cases and hoisted imports. */
|
||||||
const parseDoc = (
|
const parseDoc = (
|
||||||
name: string,
|
name: string,
|
||||||
@@ -339,6 +346,7 @@ const parseDoc = (
|
|||||||
for (const node of fromMarkdown(markdown).children) {
|
for (const node of fromMarkdown(markdown).children) {
|
||||||
handleNode(state, name, node);
|
handleNode(state, name, node);
|
||||||
}
|
}
|
||||||
|
ensurePreludeAssert(state.named);
|
||||||
return {
|
return {
|
||||||
named: state.named,
|
named: state.named,
|
||||||
passthrough: state.passthrough,
|
passthrough: state.passthrough,
|
||||||
@@ -368,7 +376,6 @@ const buildHeader = (name: string, imports: readonly string[]): string[] => {
|
|||||||
`// Source: ${name}`,
|
`// Source: ${name}`,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
PRELUDE_TEST,
|
PRELUDE_TEST,
|
||||||
PRELUDE_ASSERT,
|
|
||||||
];
|
];
|
||||||
if (imports.length > INITIAL_COUNT) {
|
if (imports.length > INITIAL_COUNT) {
|
||||||
header.push(EMPTY, ...imports);
|
header.push(EMPTY, ...imports);
|
||||||
|
|||||||
Reference in new issue
Block a user