diff --git a/src/tagged-union.test.ts b/src/tagged-union.test.ts index 36a3564..bd6f4b1 100644 --- a/src/tagged-union.test.ts +++ b/src/tagged-union.test.ts @@ -893,10 +893,10 @@ test("getTaggedUnionMatcher: the maximum illegal tag universe is rejected", () = }); // ============================================================================ -// Union-valued and optional discriminants +// Property unions // ============================================================================ -test("getTaggedUnionMatcher: a union-valued property narrows per handler", () => { +test("property union: narrows each handler", () => { // Arrange — one shape, not a union of variants; `color` is a union. interface Paint { readonly color: "red" | "green" | "blue"; @@ -939,7 +939,7 @@ test("getTaggedUnionMatcher: a union-valued property narrows per handler", () => assert.equal(describe({ color: "blue", value: 3 }), 3); }); -test("getTaggedUnionMatcher: a fallback narrows a union-valued property", () => { +test("property union: a fallback receives the unhandled values", () => { // Arrange interface Paint { color: "red" | "green" | "blue"; @@ -976,7 +976,7 @@ test("getTaggedUnionMatcher: a fallback narrows a union-valued property", () => assert.equal(describe({ color: "blue", value: 3 }), 2); }); -test("getTaggedUnionMatcher: a numeric union-valued property narrows per handler", () => { +test("property union: a numeric property narrows each handler", () => { // Arrange interface Version { code: 1 | 2 | 3; @@ -1010,7 +1010,7 @@ test("getTaggedUnionMatcher: a numeric union-valued property narrows per handler assert.equal(describe({ code: 3, value: 30 }), 3); }); -test("getTaggedUnionMatcher: an optional discriminant narrows both handlers", () => { +test("property union: an optional property is required for a defined tag", () => { // Arrange — `type?` is `"x" | undefined`; both handlers must be typed, // and matching `"x"` proves the key is present, so `type` becomes required. interface Maybe { @@ -1041,7 +1041,7 @@ test("getTaggedUnionMatcher: an optional discriminant narrows both handlers", () assert.equal(describe({ value: 2 }), 2); }); -test("getTaggedUnionMatcher: an optional discriminant fallback keeps the undefined tag", () => { +test("property union: an optional fallback receives the undefined tag", () => { // Arrange interface Maybe { type?: "x"; @@ -1074,7 +1074,7 @@ test("getTaggedUnionMatcher: an optional discriminant fallback keeps the undefin assert.equal(describe({ value: 2 }), 2); }); -test("getTaggedUnionMatcher: explicit `undefined` stays distinct from absence", () => { +test("property union: explicit `undefined` stays distinct from absence", () => { // Arrange — `type?: "x" | undefined` admits an explicit `undefined`, unlike // the bare optional above; the two representations must not be conflated. interface Explicit { @@ -1107,7 +1107,7 @@ test("getTaggedUnionMatcher: explicit `undefined` stays distinct from absence", assert.equal(describe({ value: 3 }), 2); }); -test("getTaggedUnionMatcher: a union-valued member is split, not dropped", () => { +test("property union: a union-valued member is split, not dropped", () => { // A member whose tag is itself a union sits beside a singleton-tag member. // Selecting members with `Extract>` keeps only the // singleton; the distributive narrowing must keep both sides of the @@ -1139,7 +1139,7 @@ test("getTaggedUnionMatcher: a union-valued member is split, not dropped", () => assert.equal(describe({ kind: "b", b: 3 }), 2); }); -test("getTaggedUnionMatcherW: a union-valued property widens to the union of returns", () => { +test("property union: getTaggedUnionMatcherW widens the returns", () => { // Arrange interface Paint { color: "red" | "green" | "blue"; @@ -1173,7 +1173,7 @@ test("getTaggedUnionMatcherW: a union-valued property widens to the union of ret assert.equal(describe({ color: "blue", value: 3 }), true); }); -test("getTaggedUnionMatcher: a union-valued property still enforces its contract", () => { +test("property union: enforces its contract", () => { // Arrange interface Paint { color: "red" | "green" | "blue"; @@ -1195,6 +1195,55 @@ test("getTaggedUnionMatcher: a union-valued property still enforces its contract factory({ red: () => 1, green: () => 2, blue: () => 3 }, () => 0); }); +test("property union: a broad value in the union is rejected", () => { + // Arrange — the property is a union, but not a finite union of literals: a + // template literal cannot be proven exhaustive. + interface Template { + kind: "a" | `x-${number}`; + a: number; + } + + // Act / Assert — the calls below must not compile + // @ts-expect-error a template literal is not a finite literal + getTaggedUnionMatcher