import Prando from "prando"; type Result = 'OK' | 'Falsified' | 'Exception'; type ShrinkResult = { counterexample: A, shrinks: number }; type TestResult = { result: Result, counterexample: ShrinkResult | null }; type Predicate = (a: A) => boolean; export type Gen = (rng: Prando) => ((size: number) => A); type Shrinker = (a: A) => [A]; const MAX_SUCCESS = 100; function findMinimalCounterExample(x: A, predicate: Predicate, shrinker: Shrinker, depth: number = 0): ShrinkResult { let xs = shrinker(x); let counterexample = x; let shrinks = depth; for (let y of xs) { if (!predicate(y)) { let shrink = findMinimalCounterExample(y, predicate, shrinker, depth + 1); if (shrink.shrinks > depth) { counterexample = shrink.counterexample; shrinks = shrink.shrinks; } } } return { counterexample, shrinks }; } export function property(rng: Prando, size: number, predicate: Predicate, generator: Gen, shrinker: Shrinker): TestResult { let gen = generator(rng); for (let i = 0; i < MAX_SUCCESS; i++) { let x = gen(size); if (!predicate(x)) { let counterexample = findMinimalCounterExample(x, predicate, shrinker); return { result: 'Falsified', counterexample }; } } return { result: 'OK', counterexample: null }; }