import { assert, describe, it } from "@effect/vitest" import * as Cause from "effect/Cause" import * as Effect from "effect/Effect" import * as Random from "effect/Random" describe("Random", () => { describe("next", () => { it.effect("generates a number between 0 inclusive and 1 exclusive", () => Effect.gen(function*() { const value = yield* Random.next assert.isAtLeast(value, 0) assert.isBelow(value, 1) })) }) describe("nextBoolean", () => { it.effect("generates a boolean", () => Effect.gen(function*() { const value = yield* Random.nextBoolean assert.isTrue(value === true || value === false) })) it.effect("is deterministic with the same seed", () => Effect.gen(function*() { const program = Effect.all([Random.nextBoolean, Random.nextBoolean, Random.nextBoolean]) const result1 = yield* program.pipe(Random.withSeed("next-boolean-seed")) const result2 = yield* program.pipe(Random.withSeed("next-boolean-seed")) assert.deepStrictEqual(result1, result2) })) it.effect("uses a strict greater-than threshold", () => Effect.gen(function*() { const values = [0.5, 0.500001, 0.1] let index = 0 const result = yield* Effect.all([Random.nextBoolean, Random.nextBoolean, Random.nextBoolean]).pipe( Effect.provideService(Random.Random, { nextIntUnsafe: () => 0, nextDoubleUnsafe: () => values[index++] ?? 0 }) ) assert.deepStrictEqual(result, [false, true, false]) })) }) describe("nextInt", () => { it.effect("generates a safe integer", () => Effect.gen(function*() { const value = yield* Random.nextInt assert.isTrue(Number.isSafeInteger(value)) assert.isAtLeast(value, Number.MIN_SAFE_INTEGER) assert.isAtMost(value, Number.MAX_SAFE_INTEGER) })) }) describe("nextBetween", () => { it.effect("generates number in half-open range", () => Effect.gen(function*() { for (let i = 0; i < 100; i++) { const value = yield* Random.nextBetween(10, 20) assert.isAtLeast(value, 10) assert.isBelow(value, 20) } })) it.effect("does not round the bounds", () => Effect.gen(function*() { const value = yield* Random.nextBetween(10.5, 20.5).pipe( Effect.provideService(Random.Random, { nextIntUnsafe: () => 0, nextDoubleUnsafe: () => 0.75 }) ) assert.strictEqual(value, 18) })) it.effect("handles negative ranges", () => Effect.gen(function*() { const value = yield* Random.nextBetween(-10, 10) assert.isAtLeast(value, -10) assert.isBelow(value, 10) })) }) describe("nextIntBetween", () => { it.effect("generates integer in closed range", () => Effect.gen(function*() { for (let i = 0; i < 100; i++) { const value = yield* Random.nextIntBetween(1, 6) assert.isTrue(Number.isInteger(value)) assert.isAtLeast(value, 1) assert.isAtMost(value, 6) } })) it.effect("generates integer in half-open range", () => Effect.gen(function*() { for (let i = 0; i < 100; i++) { const value = yield* Random.nextIntBetween(1, 6, { halfOpen: true }) assert.isTrue(Number.isInteger(value)) assert.isAtLeast(value, 1) assert.isBelow(value, 6) } })) it.effect("excludes the upper bound in half-open ranges", () => Effect.gen(function*() { const value = yield* Random.nextIntBetween(1, 6, { halfOpen: true }).pipe( Effect.provideService(Random.Random, { nextIntUnsafe: () => 0, nextDoubleUnsafe: () => 1 - Number.EPSILON }) ) assert.strictEqual(value, 5) })) }) describe("shuffle", () => { it.effect("returns a shuffled copy of all values", () => Effect.gen(function*() { const input = [1, 2, 3, 4, 5] const output = yield* Random.shuffle(input) assert.notStrictEqual(output, input) assert.deepStrictEqual(input, [1, 2, 3, 4, 5]) assert.deepStrictEqual(Array.from(output).sort((a, b) => a - b), [1, 2, 3, 4, 5]) })) it.effect("is deterministic with the same seed", () => Effect.gen(function*() { const program = Random.shuffle([1, 2, 3, 4, 5]) const result1 = yield* program.pipe(Random.withSeed("shuffle-seed")) const result2 = yield* program.pipe(Random.withSeed("shuffle-seed")) assert.deepStrictEqual(result1, result2) })) }) describe("choice", () => { it.effect("selects a random element from an iterable", () => Effect.gen(function*() { const value = yield* Random.choice(["a", "b", "c"]).pipe( Effect.provideService(Random.Random, { nextIntUnsafe: () => 0, nextDoubleUnsafe: () => 0.75 }) ) assert.strictEqual(value, "c") })) it.effect("fails with NoSuchElementError for an empty iterable", () => Effect.gen(function*() { const error = yield* Random.choice([]).pipe(Effect.flip) assert.isTrue(Cause.isNoSuchElementError(error)) assert.strictEqual(error.message, "Cannot select a random element from an empty array") })) it.effect("is deterministic with the same seed", () => Effect.gen(function*() { const program = Effect.all([ Random.choice([1, 2, 3, 4, 5]), Random.choice([1, 2, 3, 4, 5]), Random.choice([1, 2, 3, 4, 5]) ]) const result1 = yield* program.pipe(Random.withSeed("choice-seed")) const result2 = yield* program.pipe(Random.withSeed("choice-seed")) assert.deepStrictEqual(result1, result2) })) }) describe("withSeed", () => { it.effect("produces deterministic sequence with same seed", () => Effect.gen(function*() { const program = Effect.gen(function*() { const v1 = yield* Random.next const v2 = yield* Random.nextInt return [v1, v2] }) const result1 = yield* program.pipe(Random.withSeed("test-seed")) const result2 = yield* program.pipe(Random.withSeed("test-seed")) assert.deepStrictEqual(result1, result2) })) it.effect("produces different sequences with different seeds", () => Effect.gen(function*() { const program = Effect.gen(function*() { const v1 = yield* Random.next const v2 = yield* Random.nextInt return [v1, v2] }) const result1 = yield* program.pipe(Random.withSeed(12345)) const result2 = yield* program.pipe(Random.withSeed(67890)) assert.notDeepEqual(result1, result2) })) it.effect("distinguishes one-character string seeds", () => Effect.gen(function*() { const program = Effect.all([Random.next, Random.next, Random.next, Random.next, Random.next]) const empty = yield* program.pipe(Random.withSeed("")) const result1 = yield* program.pipe(Random.withSeed("a")) const result2 = yield* program.pipe(Random.withSeed("b")) assert.notDeepEqual(result1, empty) assert.notDeepEqual(result2, empty) assert.notDeepEqual(result1, result2) })) it.effect("distinguishes trailing partial UTF-8 words in string seeds", () => Effect.gen(function*() { const program = Effect.all([Random.next, Random.next, Random.next, Random.next, Random.next]) const pairs = [ ["1234a", "1234b"], ["1234ab", "1234ac"], ["1234abc", "1234abd"] ] as const for (const [seed1, seed2] of pairs) { const result1 = yield* program.pipe(Random.withSeed(seed1)) const result2 = yield* program.pipe(Random.withSeed(seed2)) assert.notDeepEqual(result1, result2) } })) it.effect("distinguishes astral character string seeds", () => Effect.gen(function*() { const program = Effect.all([Random.next, Random.next, Random.next, Random.next, Random.next]) const empty = yield* program.pipe(Random.withSeed("")) const result1 = yield* program.pipe(Random.withSeed("😀")) const result2 = yield* program.pipe(Random.withSeed("😁")) const result3 = yield* program.pipe(Random.withSeed("🐀")) assert.notDeepEqual(result1, empty) assert.notDeepEqual(result2, empty) assert.notDeepEqual(result1, result2) assert.notDeepEqual(result1, result3) })) it.effect("works with numeric seeds", () => Effect.gen(function*() { const program = Effect.gen(function*() { const v1 = yield* Random.next const v2 = yield* Random.nextInt return [v1, v2] }) const result1 = yield* program.pipe(Random.withSeed(12345)) const result2 = yield* program.pipe(Random.withSeed(12345)) assert.deepStrictEqual(result1, result2) })) }) })