| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Effect, Result, Schema } from "effect" |
| 3 | import * as Arbitrary from "effect/Arbitrary" |
| 4 | |
| 5 | describe("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 | }) |