UNPKG

effect-ts-laws

Version:
176 lines 6.79 kB
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