🔀 Merge chore/test-handler-runtime-arguments into main
This commit is contained in:
commit
bf711cf9fc
3 files changed
+109
-11
No files matched your search
+4
-1
@@ -66,7 +66,10 @@ before the implementation. The loop is **type → red → green → refactor**:
|
|||||||
4. **Refactor** — with the type system and the tests as the safety net, then
|
4. **Refactor** — with the type system and the tests as the safety net, then
|
||||||
`npm run verify` as the definition-of-done gate.
|
`npm run verify` as the definition-of-done gate.
|
||||||
|
|
||||||
Every test pairs an `expectTypeOf(...)` with an `assert.*`; keep them together.
|
Every test pairs an `expectTypeOf(...)` with an `assert.*`; keep them together
|
||||||
|
— including the expectations inside handler bodies, which pair with an
|
||||||
|
assertion on the value dispatch passed, not only the type it inferred (see
|
||||||
|
[development/testing.md § Handler arguments](./development/testing.md#handler-arguments)).
|
||||||
The autocomplete tests (`src/util/__tests__/lsp-completion.test.ts` for the
|
The autocomplete tests (`src/util/__tests__/lsp-completion.test.ts` for the
|
||||||
helper, `src/primitive.test.ts` for the matcher's popup) are the
|
helper, `src/primitive.test.ts` for the matcher's popup) are the
|
||||||
exception —
|
exception —
|
||||||
|
|||||||
@@ -37,6 +37,40 @@ in
|
|||||||
build step, and the runner relies on the `.ts` import-extension convention (see
|
build step, and the runner relies on the `.ts` import-extension convention (see
|
||||||
[tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
[tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
||||||
|
|
||||||
|
## Handler arguments
|
||||||
|
|
||||||
|
#### Decision (2026-09)
|
||||||
|
|
||||||
|
A handler's `expectTypeOf(shape)` is always paired with an assertion on the
|
||||||
|
argument `dispatch` actually passed: `assert.equal` where the handler runs for
|
||||||
|
one shape, `assert.ok(s === … || s === …)` over the set a `_` fallback accepts
|
||||||
|
(`assert` is imported as `strict`, so each comparison is `Object.is`). Where a
|
||||||
|
test should also prove that the _exact_ value reached the handler unchanged,
|
||||||
|
the fallback returns the shape verbatim and the call site asserts it.
|
||||||
|
|
||||||
|
#### Why
|
||||||
|
|
||||||
|
- The parameter's type is what the compiler inferred from the pattern; the
|
||||||
|
argument is what the runtime passed. Only the second can drift, and the keys
|
||||||
|
whose property name differs from their value (`true`, `null`, `1`) are
|
||||||
|
exactly where it can — see [library.md](./library.md).
|
||||||
|
- `String(shape)` at the call site keeps every type expectation and every
|
||||||
|
return-value assertion green; the argument assertions fail (10 of the 33
|
||||||
|
tests). Without them the suite never looks at the passed argument.
|
||||||
|
- Returning the shape verbatim costs a widening pattern nothing: the remainder
|
||||||
|
type joins the union of handler returns in place of a marker literal, so the
|
||||||
|
test still shows the widening it is named for.
|
||||||
|
|
||||||
|
#### Rejected
|
||||||
|
|
||||||
|
- A recorded `unknown[]` of every fallback call compared with `deepEqual`:
|
||||||
|
strong, but it couples the assertion to call order, and the sink sits three
|
||||||
|
blocks away from the value it observes.
|
||||||
|
- `typeof` checks: they cannot separate `2` from its key text `"2"`, which is
|
||||||
|
the drift a fallback with a numeric remainder can hit.
|
||||||
|
- One expected value asserted inline in a fallback: its argument is a _set_ of
|
||||||
|
shapes, so only the disjunction holds on every call.
|
||||||
|
|
||||||
## AAA ordering
|
## AAA ordering
|
||||||
|
|
||||||
The rule is in
|
The rule is in
|
||||||
|
|||||||
+71
-10
@@ -23,10 +23,12 @@ test("getMatcher: exhaustive pattern infers one common return type", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
b: (s) => {
|
b: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"b">();
|
expectTypeOf(s).toEqualTypeOf<"b">();
|
||||||
|
assert.equal(s, "b");
|
||||||
return 2;
|
return 2;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -48,10 +50,13 @@ test("getMatcher: dispatches on numeric literal keys", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
1: (n) => {
|
1: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<1>();
|
expectTypeOf(n).toEqualTypeOf<1>();
|
||||||
|
// A numeric key must not reach its handler as the string `"1"`.
|
||||||
|
assert.equal(n, 1);
|
||||||
return n + 1;
|
return n + 1;
|
||||||
},
|
},
|
||||||
2: (n) => {
|
2: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<2>();
|
expectTypeOf(n).toEqualTypeOf<2>();
|
||||||
|
assert.equal(n, 2);
|
||||||
return n * 10;
|
return n * 10;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -70,18 +75,22 @@ test("getMatcher: dispatches on mixed string and numeric keys", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
b: (s) => {
|
b: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"b">();
|
expectTypeOf(s).toEqualTypeOf<"b">();
|
||||||
|
assert.equal(s, "b");
|
||||||
return 2;
|
return 2;
|
||||||
},
|
},
|
||||||
1: (n) => {
|
1: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<1>();
|
expectTypeOf(n).toEqualTypeOf<1>();
|
||||||
|
assert.equal(n, 1);
|
||||||
return 10;
|
return 10;
|
||||||
},
|
},
|
||||||
2: (n) => {
|
2: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<2>();
|
expectTypeOf(n).toEqualTypeOf<2>();
|
||||||
|
assert.equal(n, 2);
|
||||||
return 20;
|
return 20;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -102,10 +111,14 @@ test("getMatcher: dispatches on boolean literals", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
true: (s) => {
|
true: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<true>();
|
expectTypeOf(s).toEqualTypeOf<true>();
|
||||||
|
// The `true` key is a property name; the handler must still be
|
||||||
|
// called with the boolean `true`, not the string `"true"`.
|
||||||
|
assert.equal(s, true);
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
false: (s) => {
|
false: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<false>();
|
expectTypeOf(s).toEqualTypeOf<false>();
|
||||||
|
assert.equal(s, false);
|
||||||
return 2;
|
return 2;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -124,10 +137,12 @@ test("getMatcher: dispatches on null and undefined", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
null: (s) => {
|
null: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<null>();
|
expectTypeOf(s).toEqualTypeOf<null>();
|
||||||
|
assert.equal(s, null);
|
||||||
return "null";
|
return "null";
|
||||||
},
|
},
|
||||||
undefined: (s) => {
|
undefined: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<undefined>();
|
expectTypeOf(s).toEqualTypeOf<undefined>();
|
||||||
|
assert.equal(s, undefined);
|
||||||
return "undefined";
|
return "undefined";
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -151,12 +166,14 @@ test("getMatcher: a `_` fallback receives the unhandled keys", () => {
|
|||||||
{
|
{
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return 1 as const;
|
return 1 as const;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(s) => {
|
(s) => {
|
||||||
// The fallback sees only the keys `a` did not handle.
|
// The fallback sees only the keys `a` did not handle.
|
||||||
expectTypeOf(s).toEqualTypeOf<"b" | "c">();
|
expectTypeOf(s).toEqualTypeOf<"b" | "c">();
|
||||||
|
assert.ok(s === "b" || s === "c");
|
||||||
return 2 as const;
|
return 2 as const;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -179,15 +196,20 @@ test("getMatcher: a `_` fallback routes mixed string and numeric gaps", () => {
|
|||||||
{
|
{
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return "A";
|
return "A";
|
||||||
},
|
},
|
||||||
1: (n) => {
|
1: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<1>();
|
expectTypeOf(n).toEqualTypeOf<1>();
|
||||||
|
assert.equal(n, 1);
|
||||||
return "one";
|
return "one";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(s) => {
|
(s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"b" | 2>();
|
expectTypeOf(s).toEqualTypeOf<"b" | 2>();
|
||||||
|
// The gap mixes a string and a number, so the number must reach the
|
||||||
|
// fallback as `2`, never as its key text `"2"`.
|
||||||
|
assert.ok(s === "b" || s === 2);
|
||||||
return "fallback";
|
return "fallback";
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -209,10 +231,12 @@ test("getMatcher: a `_` fallback receives unhandled boolean and nullish keys", (
|
|||||||
{
|
{
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return 1 as const;
|
return 1 as const;
|
||||||
},
|
},
|
||||||
null: (s) => {
|
null: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<null>();
|
expectTypeOf(s).toEqualTypeOf<null>();
|
||||||
|
assert.equal(s, null);
|
||||||
return 2 as const;
|
return 2 as const;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -220,6 +244,7 @@ test("getMatcher: a `_` fallback receives unhandled boolean and nullish keys", (
|
|||||||
// `true` and `false` are keyed as `"true"`/`"false"` but the
|
// `true` and `false` are keyed as `"true"`/`"false"` but the
|
||||||
// fallback still sees them as booleans.
|
// fallback still sees them as booleans.
|
||||||
expectTypeOf(s).toEqualTypeOf<true | false | undefined>();
|
expectTypeOf(s).toEqualTypeOf<true | false | undefined>();
|
||||||
|
assert.ok(s === true || s === false || s === undefined);
|
||||||
return 3 as const;
|
return 3 as const;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -247,10 +272,12 @@ test("getMatcherW: exhaustive pattern widens to the union of handler returns", (
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
x: (s) => {
|
x: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"x">();
|
expectTypeOf(s).toEqualTypeOf<"x">();
|
||||||
|
assert.equal(s, "x");
|
||||||
return 1 as const;
|
return 1 as const;
|
||||||
},
|
},
|
||||||
y: (s) => {
|
y: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"y">();
|
expectTypeOf(s).toEqualTypeOf<"y">();
|
||||||
|
assert.equal(s, "y");
|
||||||
return "two" as const;
|
return "two" as const;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -272,18 +299,22 @@ test("getMatcherW: dispatches on mixed string and numeric keys", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return "A" as const;
|
return "A" as const;
|
||||||
},
|
},
|
||||||
b: (s) => {
|
b: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"b">();
|
expectTypeOf(s).toEqualTypeOf<"b">();
|
||||||
|
assert.equal(s, "b");
|
||||||
return "B" as const;
|
return "B" as const;
|
||||||
},
|
},
|
||||||
1: (n) => {
|
1: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<1>();
|
expectTypeOf(n).toEqualTypeOf<1>();
|
||||||
|
assert.equal(n, 1);
|
||||||
return 10 as const;
|
return 10 as const;
|
||||||
},
|
},
|
||||||
2: (n) => {
|
2: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<2>();
|
expectTypeOf(n).toEqualTypeOf<2>();
|
||||||
|
assert.equal(n, 2);
|
||||||
return 20 as const;
|
return 20 as const;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -306,18 +337,22 @@ test("getMatcherW: exhaustive boolean and nullish widen to the union", () => {
|
|||||||
const matcher = factory({
|
const matcher = factory({
|
||||||
true: (s) => {
|
true: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<true>();
|
expectTypeOf(s).toEqualTypeOf<true>();
|
||||||
|
assert.equal(s, true);
|
||||||
return "yes" as const;
|
return "yes" as const;
|
||||||
},
|
},
|
||||||
false: (s) => {
|
false: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<false>();
|
expectTypeOf(s).toEqualTypeOf<false>();
|
||||||
|
assert.equal(s, false);
|
||||||
return "no" as const;
|
return "no" as const;
|
||||||
},
|
},
|
||||||
null: (s) => {
|
null: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<null>();
|
expectTypeOf(s).toEqualTypeOf<null>();
|
||||||
|
assert.equal(s, null);
|
||||||
return 0 as const;
|
return 0 as const;
|
||||||
},
|
},
|
||||||
undefined: (s) => {
|
undefined: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<undefined>();
|
expectTypeOf(s).toEqualTypeOf<undefined>();
|
||||||
|
assert.equal(s, undefined);
|
||||||
return 1 as const;
|
return 1 as const;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -345,25 +380,28 @@ test("getMatcherW: a `_` fallback widens gaps into the union", () => {
|
|||||||
{
|
{
|
||||||
x: (s) => {
|
x: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"x">();
|
expectTypeOf(s).toEqualTypeOf<"x">();
|
||||||
|
assert.equal(s, "x");
|
||||||
return 1 as const;
|
return 1 as const;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(s) => {
|
(s) => {
|
||||||
// The fallback sees only the keys `x` did not handle.
|
// The fallback sees only the keys `x` did not handle.
|
||||||
expectTypeOf(s).toEqualTypeOf<"y" | "z">();
|
expectTypeOf(s).toEqualTypeOf<"y" | "z">();
|
||||||
return "fallback" as const;
|
// Returning the shape verbatim lets the `Assert` block check the
|
||||||
|
// exact value dispatch passed.
|
||||||
|
return s;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expectTypeOf(matcher).toEqualTypeOf<
|
expectTypeOf(matcher).toEqualTypeOf<
|
||||||
(shape: "x" | "y" | "z") => 1 | "fallback"
|
(shape: "x" | "y" | "z") => 1 | "y" | "z"
|
||||||
>();
|
>();
|
||||||
// ❌ a value outside T must not be accepted by the matcher
|
// ❌ a value outside T must not be accepted by the matcher
|
||||||
expectTypeOf<"w">().not.toExtend<Parameters<typeof matcher>[0]>();
|
expectTypeOf<"w">().not.toExtend<Parameters<typeof matcher>[0]>();
|
||||||
assert.equal(matcher("x"), 1);
|
assert.equal(matcher("x"), 1);
|
||||||
assert.equal(matcher("y"), "fallback");
|
assert.equal(matcher("y"), "y");
|
||||||
assert.equal(matcher("z"), "fallback");
|
assert.equal(matcher("z"), "z");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getMatcherW: a `_` fallback widens mixed string and numeric returns", () => {
|
test("getMatcherW: a `_` fallback widens mixed string and numeric returns", () => {
|
||||||
@@ -375,28 +413,34 @@ test("getMatcherW: a `_` fallback widens mixed string and numeric returns", () =
|
|||||||
{
|
{
|
||||||
a: (s) => {
|
a: (s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"a">();
|
expectTypeOf(s).toEqualTypeOf<"a">();
|
||||||
|
assert.equal(s, "a");
|
||||||
return "A" as const;
|
return "A" as const;
|
||||||
},
|
},
|
||||||
1: (n) => {
|
1: (n) => {
|
||||||
expectTypeOf(n).toEqualTypeOf<1>();
|
expectTypeOf(n).toEqualTypeOf<1>();
|
||||||
|
assert.equal(n, 1);
|
||||||
return 10 as const;
|
return 10 as const;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(s) => {
|
(s) => {
|
||||||
expectTypeOf(s).toEqualTypeOf<"b" | 2>();
|
expectTypeOf(s).toEqualTypeOf<"b" | 2>();
|
||||||
return "fallback" as const;
|
// The number must arrive as `2`, not as its key text `"2"`.
|
||||||
|
assert.ok(s === "b" || s === 2);
|
||||||
|
// Returning the shape verbatim lets the `Assert` block check the
|
||||||
|
// exact value dispatch passed.
|
||||||
|
return s;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// ❌ ReturnsStrict: mixed handler returns widen to their union.
|
// ❌ ReturnsStrict: mixed handler returns widen to their union.
|
||||||
expectTypeOf(matcher).toEqualTypeOf<
|
expectTypeOf(matcher).toEqualTypeOf<
|
||||||
(shape: "a" | "b" | 1 | 2) => "A" | 10 | "fallback"
|
(shape: "a" | "b" | 1 | 2) => "A" | 10 | "b" | 2
|
||||||
>();
|
>();
|
||||||
assert.equal(matcher("a"), "A");
|
assert.equal(matcher("a"), "A");
|
||||||
assert.equal(matcher("b"), "fallback");
|
assert.equal(matcher("b"), "b");
|
||||||
assert.equal(matcher(1), 10);
|
assert.equal(matcher(1), 10);
|
||||||
assert.equal(matcher(2), "fallback");
|
assert.equal(matcher(2), 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -455,6 +499,10 @@ test("getMatcher: an open universe keeps the fallback's remainder open", () => {
|
|||||||
// The map's literal keys do not close an open universe, so the
|
// The map's literal keys do not close an open universe, so the
|
||||||
// remainder stays `string` and the fallback is not redundant.
|
// remainder stays `string` and the fallback is not redundant.
|
||||||
expectTypeOf(s).toEqualTypeOf<string>();
|
expectTypeOf(s).toEqualTypeOf<string>();
|
||||||
|
// A strict pattern fixes one `R` for every handler, so this fallback
|
||||||
|
// cannot return its shape; `typeof` is the strongest claim the value
|
||||||
|
// makes on its own — any string passes, including `""`.
|
||||||
|
assert.equal(typeof s, "string");
|
||||||
return 2 as const;
|
return 2 as const;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -488,7 +536,13 @@ test("getMatcherW factory rejects patterns outside its contract", () => {
|
|||||||
test("getMatcher: an unhandled shape throws without a fallback", () => {
|
test("getMatcher: an unhandled shape throws without a fallback", () => {
|
||||||
// Arrange — an open universe types its handler map as an index signature,
|
// Arrange — an open universe types its handler map as an index signature,
|
||||||
// so the type system cannot prove the runtime map is exhaustive.
|
// so the type system cannot prove the runtime map is exhaustive.
|
||||||
const handlers: Record<string, () => number> = { a: () => 1 };
|
const handlers: Record<string, (shape: unknown) => number> = {
|
||||||
|
a: (shape) => {
|
||||||
|
// The raw shape reaches the handler, not the property key's text.
|
||||||
|
assert.equal(shape, "a");
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
};
|
||||||
const matcher = getMatcher<string>()(handlers);
|
const matcher = getMatcher<string>()(handlers);
|
||||||
|
|
||||||
// Act / Assert
|
// Act / Assert
|
||||||
@@ -499,7 +553,14 @@ test("getMatcher: an unhandled shape throws without a fallback", () => {
|
|||||||
test("getMatcher: an unhandled boolean shape throws without a fallback", () => {
|
test("getMatcher: an unhandled boolean shape throws without a fallback", () => {
|
||||||
// Arrange — an `string | boolean` universe widens its handler map to an
|
// Arrange — an `string | boolean` universe widens its handler map to an
|
||||||
// index signature, so the runtime map's exhaustiveness is not provable.
|
// index signature, so the runtime map's exhaustiveness is not provable.
|
||||||
const handlers: Record<string, () => number> = { true: () => 1 };
|
const handlers: Record<string, (shape: unknown) => number> = {
|
||||||
|
true: (shape) => {
|
||||||
|
// `true` indexes the map as the property `"true"`, but the handler
|
||||||
|
// is still called with the boolean.
|
||||||
|
assert.equal(shape, true);
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
};
|
||||||
const matcher = getMatcher<string | boolean>()(handlers);
|
const matcher = getMatcher<string | boolean>()(handlers);
|
||||||
|
|
||||||
// Act / Assert
|
// Act / Assert
|
||||||
|
|||||||
Reference in new issue
Block a user