All files / src/arbitrary data.ts

100% Statements 91/91
100% Branches 12/12
100% Functions 7/7
100% Lines 91/91

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216        1x                     1x 1x     1x             1x 143x 143x   143x           1x 1306x             1x 1x 1x 1x             1x 1x 1x 1x         1x 1x 1x 1x           1x 1x 1x 1x           1x 409x           1x 1x                                                 1x           231x 231x     231x 231x         231x               1x 1x                 1x 73x 73x 73x 73x 73x 73x 73x 73x 73x 73x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x             1x 46x           1x 30x 30x 30x 30x 30x       30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x  
/**
 * Arbitraries for basic effect-ts datatypes.
 * @module
 */
import {
  Array as AR,
  Cause as CA,
  Either as EI,
  flow,
  List as LI,
  Option as OP,
  pipe,
  String as STR,
} from 'effect'
import type {Kind, TypeLambda} from 'effect/HKT'
import fc from 'fast-check'
import {Monad as arbitraryMonad} from './instances.js'
import type {LiftArbitrary} from './types.js'
 
const {map, flatMap} = arbitraryMonad
 
/**
 * Returns an `Either` arbitrary given a pair of arbitraries for the underlying
 * left and right values.
 * @category arbitraries
 */
export const either = <A, E>(
  e: fc.Arbitrary<E>,
  a: fc.Arbitrary<A>,
): fc.Arbitrary<EI.Either<A, E>> =>
  fc.oneof(a.map<EI.Either<A, E>>(EI.right), e.map<EI.Either<A, E>>(EI.left))
 
/**
 * Returns an `Option` arbitrary given an arbitrary for the underlying value.
 * @category arbitraries
 */
export const option = <A>(a: fc.Arbitrary<A>): fc.Arbitrary<OP.Option<A>> =>
  fc.oneof(pipe(a, map(OP.some)), fc.constant(OP.none<A>()))
 
/**
 * An integer arbitrary small enough so that we can avoid having to think about
 * numeric overflows in generated functions.
 * @category arbitraries
 */
export const tinyInteger: fc.Arbitrary<number> = fc.integer({
  min: -100,
  max: 100,
})
 
/**
 * A non-negative integer arbitrary small enough so that we can avoid having to
 * think about numeric overflows in generated functions.
 * @category arbitraries
 */
export const tinyNonNegative: fc.Arbitrary<number> = fc.integer({
  min: 0,
  max: 100,
})
/**
 * A arbitrary for a tiny, possibly empty, string.
 * @category arbitraries
 */
export const tinyString: fc.Arbitrary<string> = fc.string({
  minLength: 0,
  maxLength: 3,
})
 
/**
 * An integer in the range 1…100.
 * @category arbitraries
 */
export const tinyPositive: fc.Arbitrary<number> = fc.integer({
  min: 1,
  max: 100,
})
 
/**
 * An array of tiny integers with max size fixed at 4.
 * @category arbitraries
 */
export const tinyArray = <A>(a: fc.Arbitrary<A>): fc.Arbitrary<A[]> =>
  fc.array(a, {maxLength: 4})
 
/**
 * An array of tiny integers with max size fixed at 4.
 * @category arbitraries
 */
export const tinyIntegerArray: fc.Arbitrary<readonly number[]> =
  tinyArray(tinyInteger)
 
/**
 * Given a {@link LiftArbitrary} function, and 1..n `Arbitrary`s for
 * different types `A₁, A₂, ...Aₙ`, returns the given list except every
 * arbitrary for type `Aᵢ` has been replaced by an arbitrary for type
 * `Kind<F, R, O, E, Aᵢ>`. For example:
 * @example
 * import {option, liftArbitraries, tinyPositive, tinyIntegerArray} from 'effect-ts-laws'
 * import {OptionTypeLambda} from 'effect/Option'
 * import fc from 'fast-check'
 *
 * const [positive, integerArray] = liftArbitraries<OptionTypeLambda>(
 *   option,
 * )(
 *   tinyPositive,
 *   tinyIntegerArray,
 * )
 * // typeof positive     ≡ fc.Arbitrary<Option<number>>
 * // typeof integerArray ≡ fc.Arbitrary<Option<readonly number[]>>
 *
 * console.log(fc.sample(positive, {numRuns: 1}))
 * console.table(fc.sample(integerArray, {numRuns: 1}))
 * @category lifting
 */
export const liftArbitraries = <
  F extends TypeLambda,
  R = never,
  O = unknown,
  E = unknown,
>(
  liftArbitrary: LiftArbitrary<F, R, O, E>,
) => {
  type Data<T> = Kind<F, R, O, E, T>
 
  return <const Arbs extends fc.Arbitrary<unknown>[]>(...arbs: Arbs) =>
    AR.map(arbs, (arb: fc.Arbitrary<unknown>) => liftArbitrary(arb)) as {
      [K in keyof Arbs]: fc.Arbitrary<
        Data<Arbs[K] extends fc.Arbitrary<infer T> ? T : never>
      >
    }
}
 
/**
 * Build an arbitrary error from an arbitrary of a message.
 * @param message Arbitrary for the error message string.
 * @returns Arbitrary error.
 * @category arbitraries
 */
export const error: (message: fc.Arbitrary<string>) => fc.Arbitrary<Error> =
  map(s => new Error(s))
 
/**
 * Build an arbitrary record with arbitrary string keys and
 * values built from the given arbitrary.
 * @param value Arbitrary for the record values.
 * @returns Arbitrary record.
 * @category arbitraries
 */
export const stringKeyRecord = <T>(value: fc.Arbitrary<T>) =>
  pipe(
    uniqueStrings,
    flatMap(
      flow(
        AR.map(key => [key, value] as const),
        Object.fromEntries,
        fc.record,
      ),
    ),
  ) as fc.Arbitrary<Record<string, T>>
 
const uniqueStrings = fc.uniqueArray(
  fc.string({
    minLength: 1,
    maxLength: 5,
  }),
  {
    minLength: 1,
    maxLength: 5,
    comparator: STR.Equivalence,
  },
)
 
/**
 * Lift an arbitrary into an arbitrary for the effect-ts linked-list `List`
 * type.
 * @category arbitraries
 */
export const list = <A>(a: fc.Arbitrary<A>): fc.Arbitrary<LI.List<A>> =>
  tinyArray(a).map(LI.fromIterable)
 
/**
 * Arbitrary [Cause](https://effect-ts.github.io/effect/effect/Cause.ts.html).
 * @category arbitraries
 */
export const cause = <A>(
  a: fc.Arbitrary<A>,
  defect: fc.Arbitrary<unknown> = tinyInteger,
): fc.Arbitrary<CA.Cause<A>> => {
  const depthIdentifier = fc.createDepthIdentifier()
  return fc.letrec<{
    atomic: CA.Cause<A>
    composed: CA.Cause<A>
    cause: CA.Cause<A>
  }>(tie => ({
    cause: fc.oneof(
      {maxDepth: 3, depthIdentifier},
      tie('atomic'),
      tie('composed'),
    ),
    composed: map(
      fc.tuple(
        fc.oneof(
          fc.constant('sequential' as const),
          fc.constant('parallel' as const),
        ),
        fc.tuple(tie('cause'), tie('cause')),
      ),
      ([op, pair]) => CA[op](...pair),
    ),
    atomic: fc.oneof(
      fc.constant(CA.empty),
      map(defect, CA.die),
      map(a, CA.fail),
    ),
  })).cause
}