import { assertFalse, assertNone, assertSome, assertTrue, deepStrictEqual, strictEqual, throws } from "@effect/vitest/utils" import { Chunk, Equal, Equivalence, Hash, Number, Number as Num, Option, pipe, Result, String as Str } from "effect" import { describe, it } from "vitest" const gt2 = (n: number): boolean => n > 2 describe("Option", () => { it("makeReducer", () => { const R = Option.makeReducer(Number.ReducerSum) deepStrictEqual(R.combine(Option.some(1), Option.some(2)), Option.some(3)) deepStrictEqual(R.combine(Option.some(1), Option.none()), Option.some(1)) deepStrictEqual(R.combine(Option.none(), Option.some(2)), Option.some(2)) deepStrictEqual(R.combine(Option.none(), Option.none()), Option.none()) }) it("makeReducerFailFast", () => { const R = Option.makeReducerFailFast(Number.ReducerSum) deepStrictEqual(R.combine(Option.some(1), Option.some(2)), Option.some(3)) deepStrictEqual(R.combine(Option.some(1), Option.none()), Option.none()) deepStrictEqual(R.combine(Option.none(), Option.some(2)), Option.none()) deepStrictEqual(R.combine(Option.none(), Option.none()), Option.none()) deepStrictEqual(R.combine(Option.none(), R.initialValue), Option.none()) deepStrictEqual(R.combine(R.initialValue, Option.none()), Option.none()) deepStrictEqual(R.combine(Option.some(1), R.initialValue), Option.some(1)) deepStrictEqual(R.combine(R.initialValue, Option.some(1)), Option.some(1)) }) it("gen", () => { const a = Option.gen(function*() { const x = yield* Option.some(1) const y = yield* Option.some(2) return x + y }) const b = Option.gen(function*() { return 10 }) const c = Option.gen(function*() { yield* Option.some(1) yield* Option.some(2) }) const d = Option.gen(function*() { yield* Option.some(1) return yield* Option.some(2) }) const e = Option.gen(function*() { yield* Option.some(1) yield* Option.none() return yield* Option.some(2) }) const f = Option.gen(function*() { yield* Option.none() }) const g = Option.gen({ ctx: "testContext" as const }, function*() { return yield* Option.some(this.ctx) }) assertSome(a, 3) assertSome(b, 10) assertSome(c, undefined) assertSome(d, 2) assertNone(e) assertNone(f) assertSome(g, "testContext") }) it("toString", () => { strictEqual( String(Option.none()), `none()` ) strictEqual( String(Option.some(1)), `some(1)` ) strictEqual( String(Option.some(Chunk.make(1, 2, 3))), `some(Chunk([1,2,3]))` ) }) it("toJSON", () => { deepStrictEqual(Option.none().toJSON(), { _id: "Option", _tag: "None" }) deepStrictEqual(Option.some(1).toJSON(), { _id: "Option", _tag: "Some", value: 1 }) }) it("inspect", () => { if (typeof window !== "undefined") { return } // eslint-disable-next-line @typescript-eslint/no-require-imports const { inspect } = require("node:util") deepStrictEqual(inspect(Option.none()), inspect({ _id: "Option", _tag: "None" })) deepStrictEqual(inspect(Option.some(1)), inspect({ _id: "Option", _tag: "Some", value: 1 })) }) it("verify structural equality", () => { assertTrue(Equal.equals(Option.some(1), Option.some(1))) assertFalse(Equal.equals(Option.some(1), Option.some(2))) assertTrue(Equal.equals(Option.none(), Option.none())) }) it("verify hash equality", () => { strictEqual(Hash.hash(Option.some(1)), Hash.hash(Option.some(1))) strictEqual(Hash.hash(Option.some(1)) === Hash.hash(Option.some(2)), false) strictEqual(Hash.hash(Option.none()), Hash.hash(Option.none())) }) it("getSuccess", () => { assertSome(Option.getSuccess(Result.succeed(1)), 1) assertNone(Option.getSuccess(Result.fail("a"))) }) it("getFailure", () => { assertNone(Option.getFailure(Result.succeed(1))) assertSome(Option.getFailure(Result.fail("a")), "a") }) it("toRefinement", () => { const f = ( s: string | number ): Option.Option => (typeof s === "string" ? Option.some(s) : Option.none()) const isString = Option.toRefinement(f) assertTrue(isString("s")) assertFalse(isString(1)) type A = { readonly type: "A" } type B = { readonly type: "B" } type C = A | B const isA = Option.toRefinement((c) => (c.type === "A" ? Option.some(c) : Option.none())) assertTrue(isA({ type: "A" })) assertFalse(isA({ type: "B" })) }) it("isOption", () => { assertTrue(pipe(Option.some(1), Option.isOption)) assertTrue(pipe(Option.none(), Option.isOption)) assertFalse(pipe(Result.succeed(1), Option.isOption)) }) it("firstSomeOf", () => { assertNone(Option.firstSomeOf([])) assertSome(Option.firstSomeOf([Option.some(1)]), 1) assertNone(Option.firstSomeOf([Option.none()])) assertSome( Option.firstSomeOf([Option.none(), Option.none(), Option.none(), Option.none(), Option.some(1)]), 1 ) assertNone( Option.firstSomeOf([Option.none(), Option.none(), Option.none(), Option.none()]) ) }) it("orElseResult", () => { assertSome(pipe(Option.some(1), Option.orElseResult(() => Option.some(2))), Result.fail(1)) assertSome(pipe(Option.some(1), Option.orElseResult(() => Option.none())), Result.fail(1)) assertSome(pipe(Option.none(), Option.orElseResult(() => Option.some(2))), Result.succeed(2)) assertNone(pipe(Option.none(), Option.orElseResult(() => Option.none()))) }) it("orElseSome", () => { assertSome(pipe(Option.some(1), Option.orElseSome(() => 2)), 1) assertSome(pipe(Option.none(), Option.orElseSome(() => 2)), 2) }) it("getOrThrow", () => { strictEqual(pipe(Option.some(1), Option.getOrThrow), 1) throws(() => pipe(Option.none(), Option.getOrThrow), new Error("getOrThrow called on a None")) }) it("getOrThrowWith", () => { strictEqual(pipe(Option.some(1), Option.getOrThrowWith(() => new Error("Unexpected None"))), 1) throws( () => pipe(Option.none(), Option.getOrThrowWith(() => new Error("Unexpected None"))), new Error("Unexpected None") ) }) it("unit", () => { assertSome(Option.void, undefined) }) it("product", () => { const product = Option.product assertNone(product(Option.none(), Option.none())) assertNone(product(Option.some(1), Option.none())) assertNone(product(Option.none(), Option.some("a"))) assertSome(product(Option.some(1), Option.some("a")), [1, "a"]) }) it("productMany", () => { const productMany = Option.productMany assertNone(productMany(Option.none(), [])) assertSome(productMany(Option.some(1), []), [1]) assertNone(productMany(Option.some(1), [Option.none()])) assertSome(productMany(Option.some(1), [Option.some(2)]), [1, 2]) }) it("fromIterable", () => { assertNone(Option.fromIterable([])) assertSome(Option.fromIterable(["a"]), "a") }) it("map", () => { assertSome(pipe(Option.some(2), Option.map((n) => n * 2)), 4) assertNone(pipe(Option.none(), Option.map((n) => n * 2))) }) it("flatMap", () => { const f = (n: number) => Option.some(n * 2) const g = () => Option.none() assertSome(pipe(Option.some(1), Option.flatMap(f)), 2) assertNone(pipe(Option.none(), Option.flatMap(f))) assertNone(pipe(Option.some(1), Option.flatMap(g))) assertNone(pipe(Option.none(), Option.flatMap(g))) }) it("andThen", () => { assertSome(pipe(Option.some(1), Option.andThen(() => Option.some(2))), 2) assertSome(pipe(Option.some(1), Option.andThen(Option.some(2))), 2) assertSome(pipe(Option.some(1), Option.andThen(2)), 2) assertSome(pipe(Option.some(1), Option.andThen(() => 2)), 2) assertSome(pipe(Option.some(1), Option.andThen((a) => a)), 1) assertSome(Option.andThen(Option.some(1), () => Option.some(2)), 2) assertSome(Option.andThen(Option.some(1), Option.some(2)), 2) assertSome(Option.andThen(Option.some(1), 2), 2) assertSome(Option.andThen(Option.some(1), () => 2), 2) assertSome(Option.andThen(Option.some(1), (a) => a), 1) }) it("orElse", () => { const assertOrElse = ( a: Option.Option, b: Option.Option, expected: Option.Option ) => { deepStrictEqual(pipe(a, Option.orElse(() => b)), expected) } assertOrElse(Option.some(1), Option.some(2), Option.some(1)) assertOrElse(Option.some(1), Option.none(), Option.some(1)) assertOrElse(Option.none(), Option.some(2), Option.some(2)) assertOrElse(Option.none(), Option.none(), Option.none()) }) it("partitionMap", () => { const f = (n: number) => (gt2(n) ? Result.succeed(n + 1) : Result.fail(n - 1)) deepStrictEqual(pipe(Option.none(), Option.partitionMap(f)), [Option.none(), Option.none()]) deepStrictEqual(pipe(Option.some(1), Option.partitionMap(f)), [Option.some(0), Option.none()]) deepStrictEqual(pipe(Option.some(3), Option.partitionMap(f)), [Option.none(), Option.some(4)]) }) it("filterMap", () => { const f = (n: number) => (gt2(n) ? Result.succeed(n + 1) : Result.failVoid) assertNone(pipe(Option.none(), Option.filterMap(f))) assertNone(pipe(Option.some(1), Option.filterMap(f))) assertSome(pipe(Option.some(3), Option.filterMap(f)), 4) }) it("match", () => { const onNone = () => "none" const onSome = (s: string) => `some${s.length}` const match = Option.match({ onNone, onSome }) strictEqual(match(Option.none()), "none") strictEqual(match(Option.some("abc")), "some3") }) it("getOrElse", () => { strictEqual(pipe(Option.some(1), Option.getOrElse(() => 0)), 1) strictEqual(pipe(Option.none(), Option.getOrElse(() => 0)), 0) }) it("getOrNull", () => { strictEqual(Option.getOrNull(Option.none()), null) strictEqual(Option.getOrNull(Option.some(1)), 1) }) it("getOrUndefined", () => { strictEqual(Option.getOrUndefined(Option.none()), undefined) strictEqual(Option.getOrUndefined(Option.some(1)), 1) }) it("makeOrder", () => { const OS = Option.makeOrder(Str.Order) strictEqual(OS(Option.none(), Option.none()), 0) strictEqual(OS(Option.some("a"), Option.none()), 1) strictEqual(OS(Option.none(), Option.some("a")), -1) strictEqual(OS(Option.some("a"), Option.some("a")), 0) strictEqual(OS(Option.some("a"), Option.some("b")), -1) strictEqual(OS(Option.some("b"), Option.some("a")), 1) }) it("flatMapNullishOr", () => { interface X { readonly a?: { readonly b?: { readonly c?: { readonly d: number } } } } const x1: X = { a: {} } const x2: X = { a: { b: {} } } const x3: X = { a: { b: { c: { d: 1 } } } } assertNone( pipe( Option.fromNullishOr(x1.a), Option.flatMapNullishOr((x) => x.b), Option.flatMapNullishOr((x) => x.c), Option.flatMapNullishOr((x) => x.d) ) ) assertNone( pipe( Option.fromNullishOr(x2.a), Option.flatMapNullishOr((x) => x.b), Option.flatMapNullishOr((x) => x.c), Option.flatMapNullishOr((x) => x.d) ) ) assertSome( pipe( Option.fromNullishOr(x3.a), Option.flatMapNullishOr((x) => x.b), Option.flatMapNullishOr((x) => x.c), Option.flatMapNullishOr((x) => x.d) ), 1 ) }) it("fromNullishOr", () => { assertSome(Option.fromNullishOr(2), 2) assertNone(Option.fromNullishOr(null)) assertNone(Option.fromNullishOr(undefined)) }) it("liftPredicate", () => { assertNone(pipe(1, Option.liftPredicate(gt2))) assertSome(pipe(3, Option.liftPredicate(gt2)), 3) assertNone(Option.liftPredicate(1, gt2)) assertSome(Option.liftPredicate(3, gt2), 3) type Direction = "asc" | "desc" const isDirection = (s: string): s is Direction => s === "asc" || s === "desc" assertSome(pipe("asc", Option.liftPredicate(isDirection)), "asc") assertNone(pipe("foo", Option.liftPredicate(isDirection))) assertSome(Option.liftPredicate("asc", isDirection), "asc") assertNone(Option.liftPredicate("foo", isDirection)) }) it("containsWith", () => { const containsWith = Option.containsWith((self, that) => self % 2 === that % 2) assertTrue(pipe(Option.some(2), containsWith(2))) assertTrue(pipe(Option.some(4), containsWith(4))) assertTrue(pipe(Option.some(1), containsWith(3))) assertFalse(pipe(Option.none(), containsWith(2))) assertFalse(pipe(Option.some(2), containsWith(1))) }) it("contains", () => { assertFalse(pipe(Option.none(), Option.contains(2))) assertTrue(pipe(Option.some(2), Option.contains(2))) assertFalse(pipe(Option.some(2), Option.contains(1))) }) it("isNone", () => { assertTrue(Option.isNone(Option.none())) assertFalse(Option.isNone(Option.some(1))) }) it("isSome", () => { assertFalse(Option.isSome(Option.none())) assertTrue(Option.isSome(Option.some(1))) }) it("exists", () => { const predicate = (a: number) => a === 2 assertFalse(pipe(Option.none(), Option.exists(predicate))) assertFalse(pipe(Option.some(1), Option.exists(predicate))) assertTrue(pipe(Option.some(2), Option.exists(predicate))) }) it("liftNullishOr", () => { const f = Option.liftNullishOr((n: number) => (n > 0 ? n : null)) assertSome(f(1), 1) assertNone(f(-1)) }) it("liftThrowable", () => { const parse = Option.liftThrowable(JSON.parse) assertSome(parse("1"), 1) assertNone(parse("")) }) it("tap", () => { assertNone(Option.tap(Option.none(), () => Option.none())) assertNone(Option.tap(Option.some(1), () => Option.none())) assertNone(Option.tap(Option.none(), (n) => Option.some(n * 2))) assertSome(Option.tap(Option.some(1), (n) => Option.some(n * 2)), 1) }) it("filter keeps Do notation records only when the predicate passes", () => { assertSome( pipe( Option.Do, Option.bind("x", () => Option.some("a")), Option.bind("y", () => Option.some("a")), Option.filter(({ x, y }) => x === y) ), { x: "a", y: "a" } ) assertNone( pipe( Option.Do, Option.bind("x", () => Option.some("a")), Option.bind("y", () => Option.some("b")), Option.filter(({ x, y }) => x === y) ) ) }) it("zipWith", () => { assertNone(pipe(Option.none(), Option.zipWith(Option.some(2), (a, b) => a + b))) assertNone(pipe(Option.some(1), Option.zipWith(Option.none(), (a, b) => a + b))) assertSome(pipe(Option.some(1), Option.zipWith(Option.some(2), (a, b) => a + b)), 3) }) it("reduceCompact", () => { const sumCompact = Option.reduceCompact(0, Num.sum) strictEqual(sumCompact([]), 0) strictEqual(sumCompact([Option.some(2), Option.some(3)]), 5) strictEqual(sumCompact([Option.some(2), Option.none(), Option.some(3)]), 5) }) it("makeEquivalence", () => { const isEquivalent = Option.makeEquivalence(Equivalence.strictEqual()) assertTrue(isEquivalent(Option.none(), Option.none())) assertFalse(isEquivalent(Option.none(), Option.some(1))) assertFalse(isEquivalent(Option.some(1), Option.none())) assertFalse(isEquivalent(Option.some(2), Option.some(1))) assertFalse(isEquivalent(Option.some(1), Option.some(2))) assertTrue(isEquivalent(Option.some(2), Option.some(2))) }) describe("all", () => { it("tuple", () => { assertSome(Option.all([]), []) assertSome(Option.all([Option.some(1), Option.some("hello")]), [1, "hello"]) assertNone(Option.all([Option.some(1), Option.none()])) }) it("iterable", () => { assertSome(Option.all([]), []) assertNone(Option.all([Option.none()])) assertSome(Option.all([Option.some(1), Option.some(2)]), [1, 2]) assertSome(Option.all(new Set([Option.some(1), Option.some(2)])), [1, 2]) assertNone(Option.all([Option.some(1), Option.none()])) }) it("struct", () => { assertSome( Option.all({ a: Option.some(1), b: Option.some("hello") }), { a: 1, b: "hello" } ) assertNone(Option.all({ a: Option.some(1), b: Option.none() })) }) }) it(".pipe()", () => { assertSome(Option.some(1).pipe(Option.map((n) => n + 1)), 2) }) it("lift2", () => { const f = Option.lift2((a: number, b: number): number => a + b) assertNone(f(Option.none(), Option.none())) assertNone(f(Option.some(1), Option.none())) assertNone(f(Option.none(), Option.some(2))) assertSome(f(Option.some(1), Option.some(2)), 3) }) describe("do notation", () => { it("Do", () => { assertSome(Option.Do, {}) }) it("bindTo", () => { assertSome(pipe(Option.some(1), Option.bindTo("a")), { a: 1 }) assertNone(pipe(Option.none(), Option.bindTo("a"))) assertSome( pipe( Option.some(1), Option.bindTo("__proto__"), Option.bind("x", () => Option.some(2)) ), { x: 2, ["__proto__"]: 1 } ) }) it("bind", () => { assertSome(pipe(Option.some(1), Option.bindTo("a"), Option.bind("b", ({ a }) => Option.some(a + 1))), { a: 1, b: 2 }) assertNone( pipe(Option.some(1), Option.bindTo("a"), Option.bind("b", () => Option.none())) ) assertNone( pipe(Option.none(), Option.bindTo("a"), Option.bind("b", () => Option.some(2))) ) assertSome( pipe( Option.some(1), Option.bindTo("a"), Option.bind("__proto__", ({ a }) => Option.some(a + 1)), Option.bind("b", ({ a }) => Option.some(a + 1)) ), { a: 1, b: 2, ["__proto__"]: 2 } ) }) it("let", () => { assertSome(pipe(Option.some(1), Option.bindTo("a"), Option.let("b", ({ a }) => a + 1)), { a: 1, b: 2 }) assertNone( pipe(Option.none(), Option.bindTo("a"), Option.let("b", () => 2)) ) assertSome( pipe( Option.some(1), Option.bindTo("a"), Option.let("__proto__", ({ a }) => a + 1), Option.let("b", ({ a }) => a + 1) ), { a: 1, b: 2, ["__proto__"]: 2 } ) }) }) it("as", () => { assertNone(Option.none().pipe(Option.as("a"))) assertSome(Option.some(1).pipe(Option.as("a")), "a") assertNone(Option.as(Option.none(), "a")) assertSome(Option.as(Option.some(1), "a"), "a") }) it("asVoid", () => { assertNone(Option.none().pipe(Option.asVoid)) assertSome(Option.some(1).pipe(Option.asVoid), undefined) }) })