effect-ts-laws
Version:
effect-ts law testing using fast-check.
93 lines • 3.53 kB
JavaScript
/**
* @module law The Law type and functions for working with a single law.
*/
import { Option as OP, Predicate as PR } from 'effect';
import { tupled } from 'effect/Function';
import fc from 'fast-check';
/**
* Build a law from a name, a predicate, an optional note, an arbitrary for the
* predicate arguments, and optional `fast-check` runtime parameters.
* The runtime parameters are
* [documented here](https://fast-check.dev/api-reference/interfaces/Parameters.html).
* @example
* import {Law, checkLaw, tinyPositive} from 'effect-ts-laws'
* import {Option as OP} from 'effect'
*
* export const law: Law<[number, number]> = Law(
* 'sum of positives ≥ both', // • law name
* '∀a,b in N, sum=a+b: sum≥a ∧ sum≥b', // • law note
* tinyPositive, // • list of
* tinyPositive, // arbitraries that
* )( // are required for...
* (x, y) => x + y >= x && x + y >= y, // • the law predicate
* {numRuns: 10_000}, // • optional runtime config
* )
*
* assert.equal(law.name, 'sum of positives ≥ both')
* assert.deepStrictEqual(checkLaw(law), OP.none())
* @typeParam Ts - Argument type of predicate. For example, if the law
* predicate signature is `Predicate<[a: number, b: string]>`, then `T`
* would be `[a: number, b: string]`.
* @param name - Law name, shown as test label.
* @param note - String note to be shown on failure or in verbose mode.
* @param arbitraries - A tuple of arbitraries, one per predicate argument.
* @category constructors
*/
export const Law = (name, note, ...arbitraries) => (
/** Law predicate. Its argument type is encoded in `Ts`. */
predicate,
/** `fast-check` runtime parameters. */
parameters) => ({
name,
note,
predicate: tupled(predicate),
arbitrary: fc.tuple(...arbitraries),
/* v8 ignore next 1 */
...(parameters !== undefined ? { parameters } : {}),
});
/**
* Return the given law but with its predicate negated.
* @typeParam T - Argument type of law predicate. For example, if
* the law predicate type is `Predicate<[number, number]>`, then
* `T` would be `[number, string]`.
* @param law - The law to be negated.
* @returns A new `Law` object.
* @category combinators
*/
export const negateLaw = ({ predicate, ...law }) => ({ ...law, predicate: PR.not(predicate) });
/**
* Run the law and return either `None` on pass or `Some<string>` with the error
* report on fail.
*
* See also {@link vitest.testLaw | testLaw}.
* @category harness
*/
export const checkLaw = (law, parameters) => {
let failMessage = undefined;
try {
asAssert(law, parameters);
}
catch (e) {
/* v8 ignore next 2 */
if (!(e instanceof Error))
throw new Error(e);
failMessage = e.message;
}
return OP.fromNullable(failMessage);
};
/**
* Convert the law into a `fast-check` assertion.
* @typeParam Ts - Tuple type of law predicate arguments.
* @category harness
* @param law - The law to be converted.
* @param overrides - `fast-check` runtime parameters.
* @returns A void function that will throw on predicate failure.
*/
export const asAssert = ({ name, note, predicate, arbitrary, parameters }, overrides) => {
fc.assert(fc.property(arbitrary, (args) => {
if (predicate(args))
return true;
throw new Error(`${name}: ${note}`);
}), { ...parameters, ...overrides });
};
//# sourceMappingURL=law.js.map