UNPKG

effect-ts-laws

Version:
185 lines 6 kB
/** * @module lawSet The LawSet type and functions for working with_sets_ of laws. */ import { Array as AR, pipe, Tuple as TU } from 'effect'; import { checkLaw } from './law.js'; /** * 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 const LawSet = (...sets) => ( /** * Test suite name. */ name = '', /** * List of {@link Law}s that must pass for this `LawSet` to pass. */ ...laws) => ({ name, laws, sets }); /** * Just like {@link LawSet}, but with an empty list of `LawSet`s. * @category constructors */ export const lawTests = (name = '', ...laws) => ({ name, laws: laws, sets: [], }); /** * 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 const anonymousLawTests = (...laws) => lawTests('', ...laws); /** * 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 const lawSetTests = (...sets) => pipe({ name: '', laws: [], sets }, dedupe, TU.getFirst); /** * Adds a list of laws to the law set. * @category combinators */ export const addLaws = (...add) => ({ laws, ...rest }) => ({ ...rest, laws: [...laws, ...add], }); /** * 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 const addLawSets = (...them) => /** * Parent LawSet that will get a new child. */ self => { const { sets, ...rest } = self; return { ...rest, sets: [...sets, ...them] }; }; /** * 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 const checkLaws = ({ sets, laws }, parameters) => { const ownOptions = AR.map(laws, law => checkLaw(law, parameters)); return [ ...AR.flatMap(sets, lawSet => checkLaws(lawSet, parameters)), ...AR.getSomes(ownOptions), ]; }; /** * Check a list of `LawSet`s. * @returns Possibly empty array of failure messages. * @category harness */ export const checkLawSets = (parameters) => ( /** The law sets to test. */ ...sets) => checkLaws(lawSetTests(...sets), parameters); /** * 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 const filterLaws = (re) => /** * LawSet that is parent of laws to be filtered. */ ({ laws, ...rest }) => ({ ...rest, laws: laws.filter(({ name }) => re.test(name)), }); /** * 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 const filterLawsDeep = (re) => /** * LawSet to filter. */ ({ laws, sets, ...rest }) => ({ ...rest, laws: laws.filter(({ name }) => re.test(name)), sets: sets.map(filterLawsDeep(re)), }); /** * The anonymous empty LawSet. * @category constructors */ export const emptyLawSet = anonymousLawTests(); // Rebuilds the tree filtering out LawSets so that LawSet.name is unique const dedupe = ({ name = '', sets: lawSets, laws = [] }, argNames = new Set()) => { let names = argNames; const sets = []; for (const lawSet of lawSets) { const { name = '', ...rest } = lawSet; if (name === '') { sets.push(lawSet); } else { if (names.has(name)) continue; const [res, childNames] = dedupe({ name, ...rest }, names.add(name)); sets.push(res); names = childNames; } } return [{ name, sets, laws }, names]; }; //# sourceMappingURL=lawSet.js.map