effect-ts-laws
Version:
effect-ts law testing using fast-check.
176 lines • 6.79 kB
TypeScript
import type { ParameterOverrides, UnknownLaw } from './law.js';
/**
* A `LawSet` is a recursive data structure with an array of {@link Law}s
* and an array of base `LawSet`s that models the laws required
* by some function or datatype. The `LawSet` passes only if all its
* `LawSet`s and all its own laws pass.
*
* You can try to find counterexamples to a set of laws using the functions
* {@link checkLaw} and {@link checkLaws}.
*
* Functions in the `ts-effect-laws/test` entry point will try to find
* counterexamples as part of a [vitest](https://vitest.dev/) test.
*
* Laws will be deduplicated in the scope of a run of `checkLaw` and
* `checkLaws`, so that the same law will not run more than once per
* datatype.
*
* This is required because typeclass laws are arranged in a parallel `extends`
* hierarchy to the typeclasses themselves, so that a law could appear multiple
* times in a test.
*
* Consider for example the law tests for `Array`. It has instances we wish to
* check both for `Applicative` and for `Monad`. In `effect-ts`, both extend
* `Covariant`. Thus testing the `Array` instance for `Applicative` will run the
* laws for `Covariant`. But testing for the `Monad` laws will run the
* `Covariant` law tests _again_. Deduplication avoids this issue.
*
* A `LawSet` can be tested by calling `testLaws(lawSet)` inside a `vitest`
* suite, and will appear in the test results as a list of tests grouped inside
* a `describe()` block, if it has a name, or as a flat list of test blocks if
* it does not. The function must be imported from `effect-ts-laws/vitest`.
* @category model
*/
export interface LawSet {
/**
* Optional name of unit under test. Runner uses this for `describe()`
* block name. If missing, no `describe()` block is wrapped around
* child laws.
*/
name?: string;
/**
* Possibly empty list of `LawSet`s that must pass before we run the laws
* in this set.
*/
sets: LawSet[];
/**
* Possibly empty list laws that must pass for the law set to pass.
*/
laws: UnknownLaw[];
}
/**
* Assemble a set of laws for some unit under test. You can check them using the
* functions {@link checkLaw} and {@link checkLaws }. They can be tested in a
* [vitest](https://vitest.dev/) test suite using the
* [testLaw](https://middle-ages.github.io/effect-ts-laws-docs/functions/vitest.testLaw.html)
* and
* [testLaw](https://middle-ages.github.io/effect-ts-laws-docs/functions/vitest.testLaw.html)
* functions.
* @example
* import {checkLaws, Law, LawSet, tinyPositive} from 'effect-ts-laws'
*
* // A pair of laws with no law sets.
* const setA: LawSet = LawSet()(
* 'some feature under test',
* Law('law₁', '∀n ∈ ℕ: n = n', tinyPositive)(x => x === x),
* Law('law₂', '∀n ∈ ℕ: n ≤ n', tinyPositive)(x => x <= x),
* )
*
* // Another that will run “setA” _before_ its own laws
* const setB: LawSet = LawSet(setA)(
* 'another feature under test',
* Law('law₁', '∀n ∈ ℕ: n - n = 0', tinyPositive)(x => x - x === 0),
* )
*
* // Will check “setA” as a prerequisite
* assert.deepStrictEqual(checkLaws(setB), [])
* @param sets - Requirements for the new `LawSet`.
* @category constructors
*/
export declare const LawSet: (...sets: LawSet[]) => (
/**
* Test suite name.
*/
name?: string,
/**
* List of {@link Law}s that must pass for this `LawSet` to pass.
*/
...laws: UnknownLaw[]) => LawSet;
/**
* Just like {@link LawSet}, but with an empty list of `LawSet`s.
* @category constructors
*/
export declare const lawTests: (name?: string, ...laws: UnknownLaw[]) => LawSet;
/**
* Just like {@link lawTests}, but explicitly anonymous.
*
* Anonymous LawSets do not appear as a distinct group in test results. Instead
* they appear right next to their siblings.
* @param laws - Laws under test.
* @category constructors
*/
export declare const anonymousLawTests: (...laws: UnknownLaw[]) => LawSet;
/**
* Just like {@link LawSet}, but with an empty list of laws, no
* name, and the `sets` list is _deduped_ to avoid running
* the same `LawSet` twice.
* @category constructors
*/
export declare const lawSetTests: (...sets: LawSet[]) => LawSet;
/**
* Adds a list of laws to the law set.
* @category combinators
*/
export declare const addLaws: (...add: UnknownLaw[]) => ({ laws, ...rest }: LawSet) => LawSet;
/**
* Adds a required child LawSets to a parent LawSet.
* @param them - Child LawSet that will be added.
* @returns Parent LawSet with the child LawSet added.
* @category combinators
*/
export declare const addLawSets: (...them: LawSet[]) => ((lawSet: LawSet) => LawSet);
/**
* Test the law set in a pure function with no `vitest` imports involved.
*
* See also {@link vitest.testLaws | testLaws}.
* @returns Possibly empty array of failure messages.
* @category harness
*/
export declare const checkLaws: ({ sets, laws }: LawSet, parameters?: ParameterOverrides) => string[];
/**
* Check a list of `LawSet`s.
* @returns Possibly empty array of failure messages.
* @category harness
*/
export declare const checkLawSets: (parameters?: ParameterOverrides) => (
/** The law sets to test. */
...sets: LawSet[]) => string[];
/**
* Filter the laws that are direct children of a LawSet by matching a regular
* expression on the law _name_, and return the new filtered LawSet. LawSets
* that survive the filter remain untouched.
*
* Useful when you need to remove from a LawSet laws that are problematic
* perhaps because they are slow or difficult to generate.
* @example
* import {equivalenceLaws, filterLaws, tinyInteger} from 'effect-ts-laws'
* import {Number as NU, pipe} from 'effect'
*
* // Extract reflexivity law from equivalence laws.
* const reflexivity = pipe(
* {a: tinyInteger, equalsA: NU.Equivalence, F: NU.Equivalence},
* equivalenceLaws<number>,
* filterLaws(/reflexivity/),
* )
*
* assert.equal(reflexivity.laws.length, 1)
* assert.equal(reflexivity.laws[0]?.name, 'reflexivity')
* @param re - Regular expression will be matched vs. law name.
* @returns Input LawSet, but includes only laws with names matching `re`.
* @category combinators
*/
export declare const filterLaws: (re: RegExp) => ({ laws, ...rest }: LawSet) => LawSet;
/**
* Just like `filterLaws` but recursive.
* @param re - Regular expression will be matched vs. law name.
* @returns Input LawSet, but includes only laws with names matching `re`, with
* all required LawSets also filtered.
* @category combinators
*/
export declare const filterLawsDeep: (re: RegExp) => ({ laws, sets, ...rest }: LawSet) => LawSet;
/**
* The anonymous empty LawSet.
* @category constructors
*/
export declare const emptyLawSet: LawSet;
//# sourceMappingURL=lawSet.d.ts.map