import { describe, expect, test } from "vitest"; import * as t from "../index" describe(t.CaseSwitch, () => { test("cannot construct with zero cases", () => { expect(() => { new t.CaseSwitch([]) }).toThrow() }) test("type acceptance is memoized", () => { const matcher = t.when(t.str, () => 'str') .when(t.num, () => 'num') expect(matcher.accept).toBe(matcher.accept) }) }) test("returns value", () => { expect( t.match( 5, t.when(t.str, x => `hello ${x}`) .when(t.num, x => x * 2)) ).toBe(10) }) test("enforces exhaustive match", () => { const handle = (x: number | string) => { const matcher = t .when(t.value(5 as 5), (x: 5) => x + 1) .when(t.num, (x: number) => x * x) // Try commenting out this arm: compilation will fail if you do .when(t.str, x => x.length) return t.match(x, matcher) } expect(handle('foo')).toBe(3) expect(handle(5)).toBe(6) expect(handle(2)).toBe(4) }) test("throws a type error if none match", () => { class Animal {} class Dog extends Animal {} class Cat extends Animal {} const matcher = t.when(t.str, x => `hello ${x}`) .when(t.instanceOf(Dog), _ => 'dog') .when(t.instanceOf(Animal), _ => 'animal') expect(t.match(new Dog(), matcher)).toBe('dog') expect(t.match(new Cat(), matcher)).toBe('animal') expect(t.match(new Animal(), matcher)).toBe('animal') expect(() => { t.match(5 as any, matcher) }).toThrow(); }) test("exhaustiveness example with t.Kind", () => { const fn = (u: t.Kind) => t.match(u, t.when(t.instanceOf(t.Any), () => 'any') .when(t.instanceOf(t.Never), () => 'never') .when(t.instanceOf(t.SetType), () => 'set') .when(t.instanceOf(t.MapType), () => 'map') .when(t.instanceOf(t.Dict), () => 'dict') .when(t.instanceOf(t.Struct), () => 'struct') .when(t.instanceOf(t.Arr), () => 'array') .when(t.instanceOf(t.Value), () => 'value') .when(t.instanceOf(t.InstanceOf), () => 'instanceof') .when(t.instanceOf(t.TypeOf), () => 'typeof') .when(t.instanceOf(t.Either), () => 'either') .when(t.instanceOf(t.Intersection), () => 'intersection') .when(t.instanceOf(t.MergeIntersect), () => 'merge-intersect') .when(t.instanceOf(t.Validation), () => 'validation') .when(t.instanceOf(t.Is), () => 'is') .when(t.instanceOf(t.Comment), () => 'comment') .when(t.instanceOf(t.PartialStruct), () => 'partial')) expect(fn(t.any)).toBe('any') expect(fn(t.value(1).or(t.value('two')))).toBe('either') }) test("kind example", () => { // This example demonstrates that function visitTypes(type: t.Kind, pre: (x: t.Kind) => void, post: (x: t.Kind) => void) { const recur = (x: t.Kind) => visitTypes(x, pre, post) const ignore = (_: t.Kind) => {} pre(type) t.match( type, t.when(t.instanceOf(t.Any), ignore) .when(t.instanceOf(t.Never), ignore) .when(t.instanceOf(t.SetType), v => recur(v.valueType)) .when(t.instanceOf(t.MapType), v => { recur(v.keyType); recur(v.valueType) }) .when(t.instanceOf(t.Dict), v => recur(v.valueType)) .when(t.instanceOf(t.Struct), v => Object.keys(v.definition).forEach(k => recur(v.definition[k]))) .when(t.instanceOf(t.Arr), v => recur(v.elementType)) .when(t.instanceOf(t.Value), ignore) .when(t.instanceOf(t.InstanceOf), ignore) .when(t.instanceOf(t.TypeOf), ignore) .when(t.instanceOf(t.Either), v => { recur(v.l); recur(v.r) }) .when(t.instanceOf(t.Intersection), v => v.operands.forEach(recur)) .when(t.instanceOf(t.MergeIntersect), v => v.operands.forEach(recur)) .when(t.instanceOf(t.Validation), ignore) .when(t.instanceOf(t.Is), ignore) .when(t.instanceOf(t.Comment), v => recur(v.wrapped)) .when(t.instanceOf(t.PartialStruct), v => recur(v.struct)) ) post(type) } function visitType(type: t.Kind) { const pre = (_: t.Kind) => {}; const post = (_: t.Kind) => {}; visitTypes(type, pre, post); } visitType(t.subtype({ foo: t.num, bar: t.num, baz: t.subtype({ wat: t.bool, cow: t.value('wat') }).or(t.subtype({ wat: t.str, cow: t.value('str') })) })) })