import { expect, test, vi } from "vitest" import { Either, eitherToTuple, isEither, isLeft, isRight, Left, left, Right, right, } from "#Source/result/index.ts" const internalCreateLeft = (): Either => new Left("stop") const internalCreateRight = (): Either => new Right(2) test("Either.isEither identifies Either instances", () => { expect(Either.isEither(internalCreateLeft())).toBe(true) expect(Either.isEither(internalCreateRight())).toBe(true) expect(Either.isEither({ value: "stop" })).toBe(false) }) test("Either.isLeft identifies Left instances", () => { expect(Either.isLeft(internalCreateLeft())).toBe(true) expect(Either.isLeft(internalCreateRight())).toBe(false) }) test("Either.isRight identifies Right instances", () => { expect(Either.isRight(internalCreateRight())).toBe(true) expect(Either.isRight(internalCreateLeft())).toBe(false) }) test("Either.left creates a Left instance", () => { const either = Either.left("left") expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("left") }) test("Either.right creates a Right instance", () => { const either = Either.right(1) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(1) }) test("Either.pure creates a Right instance", () => { const either = Either.pure(3) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(3) }) test("Either.lift combines right values and returns the first left", () => { const map = vi.fn((values: number[]) => values.reduce((sum, value) => sum + value, 0)) const liftedRight = Either.lift( [Either.right(2), Either.right(3)], map, ) const liftedLeft = Either.lift( [ Either.right(2), Either.left("stop"), Either.left("ignored"), ], map, ) expect(liftedRight.isRight()).toBe(true) expect(liftedRight.getRight()).toBe(5) expect(liftedLeft.isLeft()).toBe(true) expect(liftedLeft.getLeft()).toBe("stop") expect(map).toHaveBeenCalledTimes(1) expect(map).toHaveBeenCalledWith([2, 3]) }) test("Left.getValue returns the left value", () => { expect(internalCreateLeft().getValue()).toBe("stop") }) test("Left.getLeft returns the left value", () => { expect(internalCreateLeft().getLeft()).toBe("stop") }) test("Left.getRight throws", () => { expect(() => internalCreateLeft().getRight()).toThrow("Cannot get right value from Left") }) test("Left.getRightOr returns the fallback value", () => { expect(internalCreateLeft().getRightOr(9)).toBe(9) }) test("Left.isLeft returns true", () => { expect(internalCreateLeft().isLeft()).toBe(true) }) test("Left.isRight returns false", () => { expect(internalCreateLeft().isRight()).toBe(false) }) test("Left.assertLeft returns itself", () => { const either = internalCreateLeft() expect(either.assertLeft()).toBe(either) }) test("Left.assertRight throws", () => { expect(() => internalCreateLeft().assertRight()).toThrow("Assert failed") }) test("Left.tap only invokes the left effect", () => { const effects = { left: vi.fn<(value: string) => void>(), right: vi.fn<(value: number) => void>(), } const either = internalCreateLeft() expect(either.tap(effects)).toBe(either) expect(effects.left).toHaveBeenCalledWith("stop") expect(effects.right).not.toHaveBeenCalled() }) test("Left.tapLeft invokes the left effect", () => { const effect = vi.fn<(value: string) => void>() const either = internalCreateLeft() expect(either.tapLeft(effect)).toBe(either) expect(effect).toHaveBeenCalledWith("stop") }) test("Left.tapRight ignores the effect", () => { const effect = vi.fn<(value: number) => void>() const either = internalCreateLeft() expect(either.tapRight(effect)).toBe(either) expect(effect).not.toHaveBeenCalled() }) test("Left.mapLeft maps the left value", () => { const either = internalCreateLeft().mapLeft((value) => `${value}!`) expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("stop!") }) test("Left.mapRight preserves the left value", () => { const effect = vi.fn((value: number) => value + 1) const either = internalCreateLeft().mapRight(effect) expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("stop") expect(effect).not.toHaveBeenCalled() }) test("Left.bimap only maps the left value", () => { const mappers = { left: vi.fn((value: string) => `${value}!`), right: vi.fn((value: number) => value + 1), } const either = internalCreateLeft().bimap(mappers) expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("stop!") expect(mappers.left).toHaveBeenCalledWith("stop") expect(mappers.right).not.toHaveBeenCalled() }) test("Left.bind preserves the left value", () => { const bind = vi.fn((value: number) => Either.right(value + 1)) const either = internalCreateLeft().bind(bind) expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("stop") expect(bind).not.toHaveBeenCalled() }) test("Left.bindAsync preserves the left value", async () => { const bind = vi.fn( async (value: number) => await Promise.resolve(Either.right(value + 1)), ) const either = await internalCreateLeft().bindAsync(bind) expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("stop") expect(bind).not.toHaveBeenCalled() }) test("Left.fold uses the left branch", () => { const result = internalCreateLeft().fold({ left: (value) => `left:${value}`, right: (value) => `right:${value}`, }) expect(result).toBe("left:stop") }) test("Left.foldAsync uses the left branch", async () => { const result = await internalCreateLeft().foldAsync({ left: async (value) => await Promise.resolve(`left:${value}`), right: async (value) => await Promise.resolve(`right:${value}`), }) expect(result).toBe("left:stop") }) test("Left.match uses the left branch", () => { const matchers = { left: vi.fn((value: string) => `left:${value}`), right: vi.fn((value: number) => value + 1), } const result = internalCreateLeft().match(matchers) expect(result).toBe("left:stop") expect(matchers.left).toHaveBeenCalledWith("stop") expect(matchers.right).not.toHaveBeenCalled() }) test("Left.matchAsync uses the left branch", async () => { const matchers = { left: vi.fn(async (value: string) => await Promise.resolve(`left:${value}`)), right: vi.fn(async (value: number) => await Promise.resolve(value + 1)), } const result = await internalCreateLeft().matchAsync(matchers) expect(result).toBe("left:stop") expect(matchers.left).toHaveBeenCalledWith("stop") expect(matchers.right).not.toHaveBeenCalled() }) test("Left.mplus returns the fallback either", () => { const result = internalCreateLeft().mplus(Either.right(9)) expect(result.isRight()).toBe(true) expect(result.getRight()).toBe(9) }) test("Left iterator yields itself and then throws", () => { const iterator = internalCreateLeft()[Symbol.iterator]() const firstStep = iterator.next() expect(firstStep.done).toBe(false) expect(Either.isEither(firstStep.value)).toBe(true) if (Either.isEither(firstStep.value) === false) { throw new TypeError("Expected Either from Left iterator") } expect(firstStep.value.isLeft()).toBe(true) expect(firstStep.value.getLeft()).toBe("stop") expect(() => iterator.next(1)).toThrow("Cannot extract right value from Left") }) test("Left async iterator yields itself and then throws", async () => { const iterator = internalCreateLeft()[Symbol.asyncIterator]() const firstStep = await iterator.next() expect(firstStep.done).toBe(false) expect(Either.isEither(firstStep.value)).toBe(true) if (Either.isEither(firstStep.value) === false) { throw new TypeError("Expected Either from Left async iterator") } expect(firstStep.value.isLeft()).toBe(true) expect(firstStep.value.getLeft()).toBe("stop") await expect(iterator.next(1)).rejects.toThrow("Cannot extract right value from Left") }) test("Right.getValue returns the right value", () => { expect(internalCreateRight().getValue()).toBe(2) }) test("Right.getLeft throws", () => { expect(() => internalCreateRight().getLeft()).toThrow("Cannot get left value from Right") }) test("Right.getRight returns the right value", () => { expect(internalCreateRight().getRight()).toBe(2) }) test("Right.getRightOr returns the right value", () => { expect(internalCreateRight().getRightOr(9)).toBe(2) }) test("Right.isLeft returns false", () => { expect(internalCreateRight().isLeft()).toBe(false) }) test("Right.isRight returns true", () => { expect(internalCreateRight().isRight()).toBe(true) }) test("Right.assertLeft throws", () => { expect(() => internalCreateRight().assertLeft()).toThrow("Assert failed") }) test("Right.assertRight returns itself", () => { const either = internalCreateRight() expect(either.assertRight()).toBe(either) }) test("Right.tap only invokes the right effect", () => { const effects = { left: vi.fn<(value: string) => void>(), right: vi.fn<(value: number) => void>(), } const either = internalCreateRight() expect(either.tap(effects)).toBe(either) expect(effects.left).not.toHaveBeenCalled() expect(effects.right).toHaveBeenCalledWith(2) }) test("Right.tapLeft ignores the effect", () => { const effect = vi.fn<(value: string) => void>() const either = internalCreateRight() expect(either.tapLeft(effect)).toBe(either) expect(effect).not.toHaveBeenCalled() }) test("Right.tapRight invokes the effect", () => { const effect = vi.fn<(value: number) => void>() const either = internalCreateRight() expect(either.tapRight(effect)).toBe(either) expect(effect).toHaveBeenCalledWith(2) }) test("Right.mapLeft preserves the right value", () => { const effect = vi.fn((value: string) => value.length) const either = internalCreateRight().mapLeft(effect) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(2) expect(effect).not.toHaveBeenCalled() }) test("Right.mapRight maps the right value", () => { const either = internalCreateRight().mapRight((value) => value + 1) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(3) }) test("Right.bimap only maps the right value", () => { const mappers = { left: vi.fn((value: string) => `${value}!`), right: vi.fn((value: number) => value + 1), } const either = internalCreateRight().bimap(mappers) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(3) expect(mappers.left).not.toHaveBeenCalled() expect(mappers.right).toHaveBeenCalledWith(2) }) test("Right.bind uses the bind result", () => { const bind = vi.fn((value: number) => Either.right(value + 3)) const either = internalCreateRight().bind(bind) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(5) expect(bind).toHaveBeenCalledWith(2) }) test("Right.bindAsync uses the bind result", async () => { const bind = vi.fn( async (value: number) => await Promise.resolve(Either.right(value + 4)), ) const either = await internalCreateRight().bindAsync(bind) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(6) expect(bind).toHaveBeenCalledWith(2) }) test("Right.fold uses the right branch", () => { const result = internalCreateRight().fold({ left: (value) => `left:${value}`, right: (value) => `right:${value}`, }) expect(result).toBe("right:2") }) test("Right.foldAsync uses the right branch", async () => { const result = await internalCreateRight().foldAsync({ left: async (value) => await Promise.resolve(`left:${value}`), right: async (value) => await Promise.resolve(`right:${value}`), }) expect(result).toBe("right:2") }) test("Right.match uses the right branch", () => { const matchers = { left: vi.fn((value: string) => `left:${value}`), right: vi.fn((value: number) => value + 1), } const result = internalCreateRight().match(matchers) expect(result).toBe(3) expect(matchers.left).not.toHaveBeenCalled() expect(matchers.right).toHaveBeenCalledWith(2) }) test("Right.matchAsync uses the right branch", async () => { const matchers = { left: vi.fn(async (value: string) => await Promise.resolve(`left:${value}`)), right: vi.fn(async (value: number) => await Promise.resolve(value + 1)), } const result = await internalCreateRight().matchAsync(matchers) expect(result).toBe(3) expect(matchers.left).not.toHaveBeenCalled() expect(matchers.right).toHaveBeenCalledWith(2) }) test("Right.mplus keeps the current either", () => { const result = internalCreateRight().mplus(Either.left("fallback")) expect(result.isRight()).toBe(true) expect(result.getRight()).toBe(2) }) test("Right iterator returns the right value immediately", () => { expect(internalCreateRight()[Symbol.iterator]().next()).toEqual({ done: true, value: 2 }) }) test("Right async iterator returns the right value immediately", async () => { await expect(internalCreateRight()[Symbol.asyncIterator]().next()).resolves.toEqual({ done: true, value: 2, }) }) test("Right iterator returns the right value immediately", () => { expect(internalCreateRight()[Symbol.iterator]().next()).toEqual({ done: true, value: 2 }) }) test("isEither delegates to Either.isEither", () => { expect(isEither(internalCreateLeft())).toBe(true) expect(isEither(null)).toBe(false) }) test("isLeft delegates to Either.isLeft", () => { expect(isLeft(internalCreateLeft())).toBe(true) expect(isLeft(internalCreateRight())).toBe(false) }) test("isRight delegates to Either.isRight", () => { expect(isRight(internalCreateRight())).toBe(true) expect(isRight(internalCreateLeft())).toBe(false) }) test("left delegates to Either.left", () => { const either = left("helper-left") expect(either.isLeft()).toBe(true) expect(either.getLeft()).toBe("helper-left") }) test("right delegates to Either.right", () => { const either = right(7) expect(either.isRight()).toBe(true) expect(either.getRight()).toBe(7) }) test("eitherToTuple converts Left and Right values into tuples", () => { expect(eitherToTuple(Either.left("left"))).toEqual(["left", undefined]) expect(eitherToTuple(Either.right(7))).toEqual([undefined, 7]) })