UNPKG

fast-check

Version:

Property based testing framework for JavaScript (like QuickCheck)

166 lines (165 loc) 5.83 kB
import { Stream } from '../../../stream/Stream.js'; import { cloneMethod, hasCloneMethod } from '../../symbols.js'; import { Value } from './Value.js'; export class Arbitrary { filter(refinement) { return new FilterArbitrary(this, refinement); } map(mapper, unmapper) { return new MapArbitrary(this, mapper, unmapper); } chain(chainer) { return new ChainArbitrary(this, chainer); } } class ChainArbitrary extends Arbitrary { constructor(arb, chainer) { super(); this.arb = arb; this.chainer = chainer; } generate(mrng, biasFactor) { const clonedMrng = mrng.clone(); const src = this.arb.generate(mrng, biasFactor); return this.valueChainer(src, mrng, clonedMrng, biasFactor); } canShrinkWithoutContext(value) { return false; } shrink(value, context) { if (this.isSafeContext(context)) { return (!context.stoppedForOriginal ? this.arb .shrink(context.originalValue, context.originalContext) .map((v) => this.valueChainer(v, context.clonedMrng.clone(), context.clonedMrng, context.originalBias)) : Stream.nil()).join(context.chainedArbitrary.shrink(value, context.chainedContext).map((dst) => { const newContext = { ...context, chainedContext: dst.context, stoppedForOriginal: true, }; return new Value(dst.value_, newContext); })); } return Stream.nil(); } valueChainer(v, generateMrng, clonedMrng, biasFactor) { const chainedArbitrary = this.chainer(v.value_); const dst = chainedArbitrary.generate(generateMrng, biasFactor); const context = { originalBias: biasFactor, originalValue: v.value_, originalContext: v.context, stoppedForOriginal: false, chainedArbitrary, chainedContext: dst.context, clonedMrng, }; return new Value(dst.value_, context); } isSafeContext(context) { return (context != null && typeof context === 'object' && 'originalBias' in context && 'originalValue' in context && 'originalContext' in context && 'stoppedForOriginal' in context && 'chainedArbitrary' in context && 'chainedContext' in context && 'clonedMrng' in context); } } class MapArbitrary extends Arbitrary { constructor(arb, mapper, unmapper) { super(); this.arb = arb; this.mapper = mapper; this.unmapper = unmapper; this.bindValueMapper = (v) => this.valueMapper(v); } generate(mrng, biasFactor) { const g = this.arb.generate(mrng, biasFactor); return this.valueMapper(g); } canShrinkWithoutContext(value) { if (this.unmapper !== undefined) { try { const unmapped = this.unmapper(value); return this.arb.canShrinkWithoutContext(unmapped); } catch { return false; } } return false; } shrink(value, context) { if (this.isSafeContext(context)) { return this.arb.shrink(context.originalValue, context.originalContext).map(this.bindValueMapper); } if (this.unmapper !== undefined) { const unmapped = this.unmapper(value); return this.arb.shrink(unmapped, undefined).map(this.bindValueMapper); } return Stream.nil(); } mapperWithCloneIfNeeded(v) { const sourceValue = v.value; const mappedValue = this.mapper(sourceValue); if (v.hasToBeCloned && ((typeof mappedValue === 'object' && mappedValue !== null) || typeof mappedValue === 'function') && Object.isExtensible(mappedValue) && !hasCloneMethod(mappedValue)) { Object.defineProperty(mappedValue, cloneMethod, { get: () => () => this.mapperWithCloneIfNeeded(v)[0] }); } return [mappedValue, sourceValue]; } valueMapper(v) { const [mappedValue, sourceValue] = this.mapperWithCloneIfNeeded(v); const context = { originalValue: sourceValue, originalContext: v.context }; return new Value(mappedValue, context); } isSafeContext(context) { return (context != null && typeof context === 'object' && 'originalValue' in context && 'originalContext' in context); } } class FilterArbitrary extends Arbitrary { constructor(arb, refinement) { super(); this.arb = arb; this.refinement = refinement; this.bindRefinementOnValue = (v) => this.refinementOnValue(v); } generate(mrng, biasFactor) { while (true) { const g = this.arb.generate(mrng, biasFactor); if (this.refinementOnValue(g)) { return g; } } } canShrinkWithoutContext(value) { return this.arb.canShrinkWithoutContext(value) && this.refinement(value); } shrink(value, context) { return this.arb.shrink(value, context).filter(this.bindRefinementOnValue); } refinementOnValue(v) { return this.refinement(v.value); } } export function isArbitrary(instance) { return (typeof instance === 'object' && instance !== null && 'generate' in instance && 'shrink' in instance && 'canShrinkWithoutContext' in instance); } export function assertIsArbitrary(instance) { if (!isArbitrary(instance)) { throw new Error('Unexpected value received: not an instance of Arbitrary'); } }