1import { assert, describe, it } from "@effect/vitest"
2import { Effect, Result, Schema } from "effect"
3import * as Arbitrary from "effect/Arbitrary"
4
5describe("Arbitrary shared shrink contexts", () => {
6 const field = Arbitrary.schema(Schema.Literals([8, 1]), { shrink: (n) => n === 8 ? [1] as const : [] })
7 const int = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 8 }))
8
9 for (
10 const [name, pair, seed, initial] of [
11 ["all tuple", Arbitrary.all([field, field]), 0, [8, 8]],
12 ["all record", Arbitrary.all({ a: field, b: field }).pipe(Arbitrary.map(({ a, b }) => [a, b])), 0, [8, 8]],
13 ["Schema tuple", Arbitrary.schema(Schema.Tuple([int, int])), 47, [8, 3]],
14 [
15 "Schema struct",
16 Arbitrary.schema(Schema.Struct({ a: int, b: int })).pipe(Arbitrary.map(({ a, b }) => [a, b])),
17 47,
18 [8, 3]
19 ]
20 ] as const
21 ) {
22 it.effect(`preserves a sibling after promoting a filtered descendant (${name})`, () =>
23 Effect.gen(function*() {
24 const arbitrary = Arbitrary.filter(pair, ([a, b]: ReadonlyArray<number>) => a === 8 || b === 1)
25 const seen: Array<ReadonlyArray<number>> = []
26 const property = (value: ReadonlyArray<number>) => {
27 seen.push(value)
28 return value[0] !== 8
29 }
30 const result = yield* Arbitrary.checkEffect(arbitrary, property, { runs: 1, seed, maxShrinks: 100 })
31 assert.strictEqual(result._tag, "Falsified")
32 if (result._tag === "Falsified") {
33 assert.deepStrictEqual(result.initialInput, initial)
34 assert.deepStrictEqual(result.shrunkInput, [8, 1])
35 assert.isTrue(seen.some(([a, b]) => a === 1 && b === 1))
36 assert.deepStrictEqual(yield* Arbitrary.checkEffect(arbitrary, property, { replay: result.replay }), result)
37 }
38 }))
39 }
40
41 const first = Arbitrary.schema(Schema.Literals([8, 27, 0, 1]), { shrink: (n) => n === 8 ? [1] as const : [] })
42 const pair = Arbitrary.all([first, field])
43 for (
44 const [name, element, seed] of [
45 ["all", pair, 25],
46 ["map", pair.pipe(Arbitrary.map((value) => value)), 25],
47 ["filter", pair.pipe(Arbitrary.filter(() => true)), 25],
48 ["filterMap", pair.pipe(Arbitrary.filterMap(Result.succeed)), 25],
49 ["flatMap source", pair.pipe(Arbitrary.flatMap(Arbitrary.Constant)), 25],
50 ["flatMap target", Arbitrary.flatMap(Arbitrary.Constant(null), () => pair), 25],
51 ["nested all", Arbitrary.all([pair, Arbitrary.Constant("tag")]).pipe(Arbitrary.map(([value]) => value)), 611],
52 ["rejecting filter", pair.pipe(Arbitrary.filter(([a, b]) => !(a === 1 && b === 8))), 25]
53 ] as const
54 ) {
55 it.effect(`preserves descendants of a command retried after deletion (${name})`, () =>
56 Effect.gen(function*() {
57 const arbitrary = Arbitrary.array(element, { minLength: 2, maxLength: 3 })
58 const initial = [[8, 8], [27, seed === 611 ? 8 : 1], [0, 8]]
59 const failures = [initial, [[8, 8], [0, 8]], [[1, 8], [0, 8]], [[1, 1], [0, 8]]]
60 .map((value) => JSON.stringify(value))
61 const property = (value: ReadonlyArray<ReadonlyArray<number>>) => !failures.includes(JSON.stringify(value))
62 const result = yield* Arbitrary.checkEffect(arbitrary, property, { runs: 1, size: 3, seed })
63 assert.strictEqual(result._tag, "Falsified")
64 if (result._tag === "Falsified") {
65 assert.deepStrictEqual(result.initialInput, initial)
66 assert.deepStrictEqual(result.shrunkInput, [[1, 1], [0, 8]])
67 assert.deepStrictEqual(yield* Arbitrary.checkEffect(arbitrary, property, { replay: result.replay }), result)
68 }
69 }))
70 }
71
72 it.effect("preserves a sibling key after exploring a rejected key change", () =>
73 Effect.gen(function*() {
74 const schema = Schema.Record(
75 Schema.String.check(Schema.isPattern(/^[a-c]$/)),
76 Schema.Null
77 ).check(Schema.isBetweenProperties(2, 2))
78 const record = Arbitrary.schema(schema)
79 const options = { runs: 1, count: 1, seed: 13, size: 2 }
80 const [initial] = yield* Arbitrary.sampleEffect(record, options)
81 const [firstKey, secondKey] = Object.keys(initial)
82 assert.notStrictEqual(firstKey, "a")
83 assert.notStrictEqual(secondKey, "a")
84 // Reject changing the first key alone. Exploring that branch tries the
85 // second key's "a" candidate too, which must remain available at the root.
86 const arbitrary = record.pipe(
87 Arbitrary.filter((value) =>
88 Object.hasOwn(value, firstKey) || Object.hasOwn(value, "a") && !Object.hasOwn(value, secondKey)
89 )
90 )
91 const property = (value: Record<string, null>) => {
92 assert.isTrue(Schema.is(schema)(value))
93 return !Object.hasOwn(value, firstKey)
94 }
95 const result = yield* Arbitrary.checkEffect(arbitrary, property, { ...options, maxShrinks: 100 })
96 assert.strictEqual(result._tag, "Falsified")
97 if (result._tag === "Falsified") {
98 assert.deepStrictEqual(result.initialInput, initial)
99 assert.deepStrictEqual(result.shrunkInput, { [firstKey]: null, a: null })
100 assert.deepStrictEqual(yield* Arbitrary.checkEffect(arbitrary, property, { replay: result.replay }), result)
101 }
102 }))
103
104 it.effect("continues in field order while promoting rejected product descendants", () =>
105 Effect.gen(function*() {
106 const field = Arbitrary.schema(Schema.Literals([1, 0]), { shrink: (n) => n === 1 ? [0] as const : [] })
107 const arbitrary = Arbitrary.all(Array.from({ length: 7 }, () => field)).pipe(
108 Arbitrary.filter((value) => value[0] === 1)
109 )
110 const result = yield* Arbitrary.checkEffect(arbitrary, () => false, { runs: 1, seed: 198, maxShrinks: 100 })
111 assert.strictEqual(result._tag, "Falsified")
112 if (result._tag === "Falsified") {
113 assert.deepStrictEqual(result.initialInput, [1, 1, 1, 1, 1, 1, 1])
114 assert.deepStrictEqual(result.shrunkInput, [1, 0, 0, 0, 0, 0, 0])
115 }
116 }))
117})