From 3046d85284110f1101b5841d81080a38bf9cfaee Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 8 Sep 2025 14:26:31 -0700 Subject: [PATCH 1/3] Add expect and inspect to Result type. Add inspect to Option type --- src/option/index.ts | 10 ++ src/option/option.test.ts | 329 +++++++++++++++++++++++--------------- src/result/index.ts | 26 +++ src/result/result.test.ts | 283 ++++++++++++++++++++------------ 4 files changed, 416 insertions(+), 232 deletions(-) diff --git a/src/option/index.ts b/src/option/index.ts index c79b641..9df866d 100644 --- a/src/option/index.ts +++ b/src/option/index.ts @@ -77,6 +77,12 @@ export interface OptionUtils { * @returns The value of the `Option` if it is a `Some`. */ expect(message: string): T; + /** + * Peek into the inner value of this Option, and if `isSome()` then the callback function will be run. + * @param callback the callback to run if this Option `isSome` + * @returns this `Option` + */ + inspect(callback: (value: T) => void): Option; /** * Converts this `Option` type into a `Result` type. The `Result` will be an `Err` if the `Option` is a `None`. * @param error The error to return if the `Option` is a `None`. @@ -146,6 +152,10 @@ function buildOption(innerType: Some | None): Option { if (this.isSome()) return this.value; throw new OptionIsEmptyError(message); }, + inspect(callback) { + if (this.isSome()) callback(this.value); + return this; + }, okOr(error) { if (this.isNone()) { if (error instanceof Error) return result.err(error); diff --git a/src/option/option.test.ts b/src/option/option.test.ts index c4a11b6..f88014c 100644 --- a/src/option/option.test.ts +++ b/src/option/option.test.ts @@ -1,212 +1,279 @@ import { OptionIsEmptyError, option } from "."; describe("src/utility/option.ts", () => { - it("Constructs an Option type with a provided value", () => { - const optionValue = option.some(1); + describe("some", () => { + it("Constructs an Option type with a provided value", () => { + const optionValue = option.some(1); - expect(optionValue.isSome()).toBeTruthy(); + expect(optionValue.isSome()).toBeTruthy(); + }); }); - it("Constructs an Option with no provided value", () => { - const optionValue = option.none(); + describe("none", () => { + it("Constructs an Option with no provided value", () => { + const optionValue = option.none(); - expect(optionValue.isNone()).toBeTruthy(); + expect(optionValue.isNone()).toBeTruthy(); + }); }); - it("Throws an error on unwrap() if the Option is empty", () => { - const optionValue = option.none(); + describe("unwrap", () => { + it("Throws an error on unwrap() if the Option is empty", () => { + const optionValue = option.none(); - expect(() => optionValue.unwrap()).toThrow(OptionIsEmptyError); - }); + expect(() => optionValue.unwrap()).toThrow(OptionIsEmptyError); + }); - it("Provdes a default value on unwrapOrDefault() if the Option is empty", () => { - const optionValue = option.none(); + it("Returns the inner value if called on a Some variant", () => { + const optValue = option.some(123); - expect(optionValue.unwrapOr(3)).toEqual(3); + expect(() => optValue.unwrap()).not.toThrow(); + + expect(optValue.unwrap()).toEqual(123); + }); }); - it("Throws an error if the argument for unwrapOr() is undefined or null", () => { - const optionValue = option.none(); + describe("unwrapOr", () => { + it("Provdes a default value on unwrapOrDefault() if the Option is empty", () => { + const optionValue = option.none(); - expect(() => optionValue.unwrapOr(null as never)).toThrow( - OptionIsEmptyError, - ); - }); + expect(optionValue.unwrapOr(3)).toEqual(3); + }); - it("Returns the contained value when unwrap() is called with a value value in the Option", () => { - const optionValue = option.some(3); + it("Throws an error if the argument for unwrapOr() is undefined or null", () => { + const optionValue = option.none(); - expect(optionValue.unwrap()).toEqual(3); - }); + expect(() => optionValue.unwrapOr(null as never)).toThrow( + OptionIsEmptyError, + ); + }); - it("Returns the contained value when unwrapOrDefault() is called with a valid value in the Option", () => { - const optionValue = option.some(3); + it("Returns the contained value when called with a valid value in the Option", () => { + const optionValue = option.some(3); - expect(optionValue.unwrapOr(2)).toEqual(3); + expect(optionValue.unwrapOr(2)).toEqual(3); + }); }); - it("Allows direct access of the value if the Option is a Some", () => { - const optionValue = option.some(3); + describe("option.value", () => { + it("Allows direct access of the value if the Option is a Some", () => { + const optionValue = option.some(3); - expect(optionValue).toHaveProperty("value"); - }); + expect(optionValue).toHaveProperty("value"); + }); - it("Does not have the value property if the Option is a None", () => { - const optionValue = option.none(); + it("Does not have the value property if the Option is a None", () => { + const optionValue = option.none(); - expect(optionValue).not.toHaveProperty("value"); + expect(optionValue).not.toHaveProperty("value"); + }); }); - it("Transforms the Option into a Result Failure when calling okOr() on a None type", () => { - const optionValue = option.none(); + describe("okOr", () => { + it("Transforms the Option into a Result Failure when calling okOr() on a None type", () => { + const optionValue = option.none(); - expect(optionValue.okOr(new Error()).isError()).toBeTruthy(); - }); + expect(optionValue.okOr(new Error()).isError()).toBeTruthy(); + }); - it("Transforms the Option into a Result Success with calling okOr() on a Some type", () => { - const optionValue = option.some(2); + it("Transforms the Option into a Result Success with calling okOr() on a Some type", () => { + const optionValue = option.some(2); - expect(optionValue.okOr(new Error()).isOk()).toBeTruthy(); - }); + expect(optionValue.okOr(new Error()).isOk()).toBeTruthy(); + }); - it("Constructs a base Error class if okOr() is called with a string type", () => { - const optionValue = option.none(); + it("Constructs a base Error class if okOr() is called with a string type", () => { + const optionValue = option.none(); - expect(optionValue.okOr("This is an error").isError()).toBeTruthy(); + expect(optionValue.okOr("This is an error").isError()).toBeTruthy(); + }); }); - it("Throws an error using the provided message when calling expect() on a None type", () => { - const optionValue = option.none(); + describe("expect", () => { + it("Throws an error using the provided message when calling expect() on a None type", () => { + const optionValue = option.none(); - expect(() => optionValue.expect("This is an error")).toThrow( - "This is an error", - ); - }); + expect(() => optionValue.expect("This is an error")).toThrow( + "This is an error", + ); + }); - it("Doesn't throw an error when calling expect() on a Some type", () => { - const optionValue = option.some(2); + it("Doesn't throw an error when calling expect() on a Some type", () => { + const optionValue = option.some(2); - expect(() => optionValue.expect("This is an error")).not.toThrow(); + expect(() => optionValue.expect("This is an error")).not.toThrow(); + }); }); - it("Returns a None type if map() is called on an existing None type", () => { - const optionValue = option.none(); + describe("inspect", () => { + it("calls the callback if called on a Some variant", () => { + const spy = jest.fn(); + option.some(123).inspect((val) => spy(val)); - expect(optionValue.map((value) => value + 1).isNone()).toBeTruthy(); - }); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(123); + }); - it("Returns a Some if map() is called on a Some type, having applied the mapped function to the inner type", () => { - const optionValue = option.some(1); + it("does not call the callback if called on a None variant", () => { + const spy = jest.fn(); + option.none().inspect((val) => spy(val)); - expect(optionValue.map((value) => value + 1).unwrap()).toEqual(2); - }); + expect(spy).not.toHaveBeenCalled(); + }); - it("Creates a serializable Option when serialize() is called on an Option type", () => { - const optionValue = option.some(1); + it("returns the same instance when called on a Some variant", () => { + const spy = jest.fn(); + const opt = option.some(123); + const inspectOpt = opt.inspect((val) => spy(val)); - expect(optionValue.serialize()).toEqual({ - _marker: 0, - value: 1, + expect(spy).toHaveBeenCalledWith(123); + expect(opt).toEqual(inspectOpt); }); - const noneOptionValue = option.none(); + it("returns the same instance when called on a None variant", () => { + const spy = jest.fn(); + const opt = option.none(); + const inspectOpt = opt.inspect((val) => spy(val)); - expect(noneOptionValue.serialize()).toEqual({ - _marker: 1, + expect(spy).not.toHaveBeenCalled(); + expect(opt).toEqual(inspectOpt); }); }); - it("Converts a SerializableOption back into an Option when calling fromSerializableOption()", () => { - const serializable = option.some(1).serialize(); + describe("map", () => { + it("Returns a None type if map() is called on an existing None type", () => { + const optionValue = option.none(); - expect(option.fromSerializableOption(serializable).isSome()).toBeTruthy(); + expect(optionValue.map((value) => value + 1).isNone()).toBeTruthy(); + }); - const noneSerializable = option.none().serialize(); + it("Returns a Some if map() is called on a Some type, having applied the mapped function to the inner type", () => { + const optionValue = option.some(1); - expect( - option.fromSerializableOption(noneSerializable).isNone(), - ).toBeTruthy(); + expect(optionValue.map((value) => value + 1).unwrap()).toEqual(2); + }); }); - it("Returns a None type if `fromSerializableOption()` is called with no object or undefined", () => { - const result = option.fromSerializableOption(undefined); + describe("serialize", () => { + it("Creates a serializable Option when serialize() is called on an Option type", () => { + const optionValue = option.some(1); + + expect(optionValue.serialize()).toEqual({ + _marker: 0, + value: 1, + }); - expect(result.isNone()).toBeTruthy(); + const noneOptionValue = option.none(); + + expect(noneOptionValue.serialize()).toEqual({ + _marker: 1, + }); + }); }); - it.each([[1], [{}], ["testString"], [[]]])( - "Returns a None type if `fromSerializableOption()` is called with a non-Option type", - (testValue) => { - const result = option.fromSerializableOption(testValue as never); + describe("fromSerializableOption", () => { + it("Converts a SerializableOption back into an Option when calling fromSerializableOption()", () => { + const serializable = option.some(1).serialize(); - expect(result.isNone()).toBeTruthy(); - }, - ); + expect(option.fromSerializableOption(serializable).isSome()).toBeTruthy(); - it("Performs andThen on a Some type, returning a new Option", () => { - const optionValue = option.some(1); + const noneSerializable = option.none().serialize(); - const newOption = optionValue.andThen((val) => option.some(val + 1)); + expect( + option.fromSerializableOption(noneSerializable).isNone(), + ).toBeTruthy(); + }); + + it("Returns a None type if `fromSerializableOption()` is called with no object or undefined", () => { + const result = option.fromSerializableOption(undefined); - expect(newOption.unwrap()).toEqual(2); + expect(result.isNone()).toBeTruthy(); + }); + + it.each([[1], [{}], ["testString"], [[]]])( + "Returns a None type if `fromSerializableOption()` is called with a non-Option type", + (testValue) => { + const result = option.fromSerializableOption(testValue as never); + + expect(result.isNone()).toBeTruthy(); + }, + ); }); - it("Does not perform andThen on a None type, returning a None type", () => { - const optionValue = option.none(); + describe("andThen", () => { + it("Performs andThen on a Some type, returning a new Option", () => { + const optionValue = option.some(1); - const newOption = optionValue.andThen((val) => option.some(val + 1)); + const newOption = optionValue.andThen((val) => option.some(val + 1)); - expect(newOption.isNone()).toBeTruthy(); + expect(newOption.unwrap()).toEqual(2); + }); + + it("Does not perform andThen on a None type, returning a None type", () => { + const optionValue = option.none(); + + const newOption = optionValue.andThen((val) => option.some(val + 1)); + + expect(newOption.isNone()).toBeTruthy(); + }); }); - it("Constructs an Option type from an unknown value, returning a Some if the value is not null or undefined, otherwise returning None", () => { - const testValue = "test"; - const result = option.unknown(testValue); - expect(result.isSome()).toBeTruthy(); - expect(result.unwrap()).toEqual(testValue); - const nullValue: unknown = null; - const nullResult = option.unknown(nullValue); - expect(nullResult.isNone()).toBeTruthy(); + describe("option.unknown", () => { + it("Constructs an Option type from an unknown value, returning a Some if the value is not null or undefined, otherwise returning None", () => { + const testValue = "test"; + const result = option.unknown(testValue); + expect(result.isSome()).toBeTruthy(); + expect(result.unwrap()).toEqual(testValue); + const nullValue: unknown = null; + const nullResult = option.unknown(nullValue); + expect(nullResult.isNone()).toBeTruthy(); + }); }); - it.each([ - ["option.some", option.some(123), option.isSome as (val: any) => boolean], - [ - "option.some.serialize", - option.some(123).serialize(), - option.isSome as (val: any) => boolean, - ], - ["option.none", option.none(), option.isNone as (val: any) => boolean], - [ - "option.none.serialize", - option.none().serialize(), - option.isNone as (val: any) => boolean, - ], - ])("Properly checks the return type of %s", (_, optVal, validator) => { - expect(validator(optVal)).toBeTruthy(); + describe("option.isSome", () => { + it.each([ + ["option.some", option.some(123), option.isSome as (val: any) => boolean], + [ + "option.some.serialize", + option.some(123).serialize(), + option.isSome as (val: any) => boolean, + ], + ["option.none", option.none(), option.isNone as (val: any) => boolean], + [ + "option.none.serialize", + option.none().serialize(), + option.isNone as (val: any) => boolean, + ], + ])("Properly checks the return type of %s", (_, optVal, validator) => { + expect(validator(optVal)).toBeTruthy(); + }); }); - it("Returns `undefined` if unsafeUnwrap() was called on a None type", () => { - const opt = option.unknown(undefined); + describe("unsafeUnwrap", () => { + it("Returns `undefined` if unsafeUnwrap() was called on a None type", () => { + const opt = option.unknown(undefined); - expect(opt.unsafeUnwrap()).toEqual(undefined); - }); + expect(opt.unsafeUnwrap()).toEqual(undefined); + }); - it("Returns the inner value of Some if unsafeUnwrap was called on a Some type", () => { - const opt = option.some(123); + it("Returns the inner value of Some if unsafeUnwrap was called on a Some type", () => { + const opt = option.some(123); - expect(opt.unsafeUnwrap()).toEqual(123); + expect(opt.unsafeUnwrap()).toEqual(123); + }); }); - it("returns the raw default value if unsafeUnwrapOr() was called on a None type", () => { - const opt = option.unknown(undefined); + describe("unsafeUnwrapOr", () => { + it("returns the raw default value if unsafeUnwrapOr() was called on a None type", () => { + const opt = option.unknown(undefined); - expect(opt.unsafeUnwrapOr(null)).toEqual(null); - }); + expect(opt.unsafeUnwrapOr(null)).toEqual(null); + }); - it("Returns the inner value of Some if unsafeUnwrapOr() was called on a Some type", () => { - const opt = option.some(123); + it("Returns the inner value of Some if unsafeUnwrapOr() was called on a Some type", () => { + const opt = option.some(123); - expect(opt.unsafeUnwrapOr(null)).toEqual(123); + expect(opt.unsafeUnwrapOr(null)).toEqual(123); + }); }); }); diff --git a/src/result/index.ts b/src/result/index.ts index fc9b646..3714bf5 100644 --- a/src/result/index.ts +++ b/src/result/index.ts @@ -70,6 +70,20 @@ export interface ResultUtils { * @returns The inner type if it was a `Success` */ unwrapOrElse(error: E): T; + /** + * Unwraps the inner value of this `Result`. If this Result was an error variant, then an + * error will be thrown with the message specified. + * @param message The message to set on an `Error` if this Result is not a Success variant. + * @returns The inner value of this Result if this Result is a Success variant. + * @throws {Error} - If this result was a Failure variant. + */ + expect(message: string): T; + /** + * Peek into the inner value of this Result, and if `isOk()` then the callback function will be run. + * @param callback the callback to run if this Result `isOk` + * @returns this `Result` + */ + inspect(callback: (value: T) => void): Result; /** * Useful if you do not care if there is an error, you only want to know if there is a value. * @returns An `Option` if the inner type was a `Success`, otherwise `None` @@ -152,6 +166,18 @@ function buildResult( } return option.none(); }, + expect(message) { + if (this.isError()) { + throw new Error(message); + } + return this.value; + }, + inspect(callback) { + if (this.isOk()) { + callback(this.value); + } + return this; + }, mapOk(fn: (value: T) => NewT): Result { if (this.isOk()) { return ok(fn(this.value)); diff --git a/src/result/result.test.ts b/src/result/result.test.ts index c03b621..217c8f8 100644 --- a/src/result/result.test.ts +++ b/src/result/result.test.ts @@ -6,173 +6,254 @@ class NewErrorClass extends Error { } } describe("src/utility/result.ts", () => { - it("Calling unwrap() on a Success provides the Success value", () => { - const resultValue = result.ok(1); + describe("unwrap", () => { + it("Calling unwrap() on a Success provides the Success value", () => { + const resultValue = result.ok(1); - expect(resultValue.unwrap()).toEqual(1); - }); + expect(resultValue.unwrap()).toEqual(1); + }); - it("Calling unwrap() on a Failure provides the Failure error", () => { - const resultValue = result.err(new NewErrorClass()); + it("Calling unwrap() on a Failure provides the Failure error", () => { + const resultValue = result.err(new NewErrorClass()); - expect(() => resultValue.unwrap()).toThrow(NewErrorClass); + expect(() => resultValue.unwrap()).toThrow(NewErrorClass); + }); }); - it("Returns if the Result is a Success", () => { - const resultValue = result.ok(3); + describe("isOk", () => { + it("Returns if the Result is a Success", () => { + const resultValue = result.ok(3); - expect(resultValue.isOk()).toBeTruthy(); + expect(resultValue.isOk()).toBeTruthy(); + }); }); - it("Returns if the Result is a Failure", () => { - const resultValue = result.err(new Error()); + describe("isError", () => { + it("Returns if the Result is a Failure", () => { + const resultValue = result.err(new Error()); - expect(resultValue.isError()).toBeTruthy(); + expect(resultValue.isError()).toBeTruthy(); + }); }); - it("Provides a defaultValue when unwrapOr() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + describe("unwrapOr", () => { + it("Provides a defaultValue when unwrapOr() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect(resultValue.unwrapOr(2)).toEqual(2); - }); + expect(resultValue.unwrapOr(2)).toEqual(2); + }); - it("Provides the Success value if unwrapOr() is called on a Success type", () => { - const resultValue = result.ok(2); + it("Provides the Success value if unwrapOr() is called on a Success type", () => { + const resultValue = result.ok(2); - expect(resultValue.unwrapOr(5)).toEqual(2); + expect(resultValue.unwrapOr(5)).toEqual(2); + }); }); - it("Throws the supplied Error if unwrapOrElse() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + describe("unwrapOrElse", () => { + it("Throws the supplied Error if unwrapOrElse() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect(() => resultValue.unwrapOrElse(new NewErrorClass())).toThrow( - NewErrorClass - ); - }); + expect(() => resultValue.unwrapOrElse(new NewErrorClass())).toThrow( + NewErrorClass, + ); + }); - it("Provides the Success value if unwrapOrElse() is called on a Success type", () => { - const resultValue = result.ok(3); + it("Provides the Success value if unwrapOrElse() is called on a Success type", () => { + const resultValue = result.ok(3); - expect(resultValue.unwrapOrElse(new NewErrorClass())).toEqual(3); + expect(resultValue.unwrapOrElse(new NewErrorClass())).toEqual(3); + }); }); - it("Converts the Success to an Option if ok() is called on a Success type", () => { - const resultValue = result.ok(3); + describe("ok", () => { + it("Converts the Success to an Option if ok() is called on a Success type", () => { + const resultValue = result.ok(3); - expect(resultValue.ok().isSome()).toBeTruthy(); - }); + expect(resultValue.ok().isSome()).toBeTruthy(); + }); - it("Converts the Failure to an Option if ok() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + it("Converts the Failure to an Option if ok() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect(resultValue.ok().isNone()).toBeTruthy(); + expect(resultValue.ok().isNone()).toBeTruthy(); + }); }); - it("Converts the Success to an Option if err() is called on a Success type", () => { - const resultValue = result.ok(2); + describe("err", () => { + it("Converts the Success to an Option if err() is called on a Success type", () => { + const resultValue = result.ok(2); - expect(resultValue.err().isNone()).toBeTruthy(); - }); + expect(resultValue.err().isNone()).toBeTruthy(); + }); - it("Converts the Failure to an Option if err() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + it("Converts the Failure to an Option if err() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect(resultValue.err().isSome()).toBeTruthy(); + expect(resultValue.err().isSome()).toBeTruthy(); + }); }); - it("Maps the Success value to a new value if mapOk() is called on a Success type", () => { - const resultValue = result.ok(2); + describe("mapOk", () => { + it("Maps the Success value to a new value if mapOk() is called on a Success type", () => { + const resultValue = result.ok(2); - expect(resultValue.mapOk((x) => x.toString()).unwrap()).toEqual("2"); - }); + expect(resultValue.mapOk((x) => x.toString()).unwrap()).toEqual("2"); + }); - it("Does not map the Success value if mapOk() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + it("Does not map the Success value if mapOk() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect(resultValue.mapOk((x) => x * 2).isError()).toBeTruthy(); + expect(resultValue.mapOk((x) => x * 2).isError()).toBeTruthy(); + }); }); - it("Maps the Success value to a new Result if andThen() is called on a Success type", () => { - const resultValue = result.ok(2); + describe("mapErr", () => { + it("Maps the Failure error to a new error if mapErr() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect( - resultValue.andThen((x) => result.ok(x.toString())).unwrap() - ).toEqual("2"); - }); + expect( + resultValue.mapErr(() => new NewErrorClass()).isError(), + ).toBeTruthy(); + }); - it("Does not map the Success value if andThen() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + it("Does not map the Failure error if mapErr() is called on a Success type", () => { + const resultValue = result.ok(2); - expect(resultValue.andThen((x) => result.ok(x * 2)).isError()).toBeTruthy(); + expect(resultValue.mapErr(() => new NewErrorClass()).isOk()).toBeTruthy(); + }); }); - it("Maps the Failure error to a new error if mapErr() is called on a Failure type", () => { - const resultValue = result.err(new Error()); + describe("andThen", () => { + it("Maps the Success value to a new Result if andThen() is called on a Success type", () => { + const resultValue = result.ok(2); - expect( - resultValue.mapErr(() => new NewErrorClass()).isError() - ).toBeTruthy(); - }); + expect( + resultValue.andThen((x) => result.ok(x.toString())).unwrap(), + ).toEqual("2"); + }); - it("Does not map the Failure error if mapErr() is called on a Success type", () => { - const resultValue = result.ok(2); + it("Does not map the Success value if andThen() is called on a Failure type", () => { + const resultValue = result.err(new Error()); - expect(resultValue.mapErr(() => new NewErrorClass()).isOk()).toBeTruthy(); + expect( + resultValue.andThen((x) => result.ok(x * 2)).isError(), + ).toBeTruthy(); + }); }); - it("Constructs a Failire error with a default Error if provided with a string instead of an Error", () => { - const resultValue = result.err("This is an error"); + describe("err Constructor", () => { + it("Constructs a Failire error with a default Error if provided with a string instead of an Error", () => { + const resultValue = result.err("This is an error"); - expect(() => resultValue.unwrap()).toThrow(Error); - }); + expect(() => resultValue.unwrap()).toThrow(Error); + }); - it("Constructs a Failure error with an Error with no message if err() is called with no parameters", () => { - const resultValue = result.err(); + it("Constructs a Failure error with an Error with no message if err() is called with no parameters", () => { + const resultValue = result.err(); - expect(() => resultValue.unwrap()).toThrow(Error); + expect(() => resultValue.unwrap()).toThrow(Error); + }); }); - it("Constructs a Result from a promise when calling fromPromise() with a promise that resolves", async () => { - const resultValue = await result.fromPromise(Promise.resolve(3)); + describe("fromPromise", () => { + it("Constructs a Result from a promise when calling fromPromise() with a promise that resolves", async () => { + const resultValue = await result.fromPromise(Promise.resolve(3)); - expect(resultValue.unwrap()).toEqual(3); - }); + expect(resultValue.unwrap()).toEqual(3); + }); - it("Constructs a Result from a promise when calling fromPromise() with a promise that rejects", async () => { - const resultValue = await result.fromPromise( - Promise.reject(new NewErrorClass()) - ); + it("Constructs a Result from a promise when calling fromPromise() with a promise that rejects", async () => { + const resultValue = await result.fromPromise( + Promise.reject(new NewErrorClass()), + ); - expect(() => resultValue.unwrap()).toThrow(NewErrorClass); - }); + expect(() => resultValue.unwrap()).toThrow(NewErrorClass); + }); - it("Constructs a Result from a promise that throws something other than an Error", async () => { - const resultValue = await result.fromPromise(Promise.reject(2)); + it("Constructs a Result from a promise that throws something other than an Error", async () => { + const resultValue = await result.fromPromise(Promise.reject(2)); - expect(() => resultValue.unwrap()).toThrow(UnknownError); - }); + expect(() => resultValue.unwrap()).toThrow(UnknownError); + }); + + it("Constructs a Result from a Result containing a promise that resolves", async () => { + const resultValue = result.ok(Promise.resolve(3)); - it("Constructs a Result from a Result containing a promise that resolves", async () => { - const resultValue = result.ok(Promise.resolve(3)); + const finalResult = await result.fromPromise(resultValue); - const finalResult = await result.fromPromise(resultValue); + expect(finalResult.unwrap()).toEqual(3); + }); - expect(finalResult.unwrap()).toEqual(3); + it("Constructs a Result from a Result containing a promise that rejects", async () => { + const resultValue = result.ok(Promise.reject(new NewErrorClass())); + + const finalResult = await result.fromPromise(resultValue); + + expect(() => finalResult.unwrap()).toThrow(NewErrorClass); + }); + + it("Constructs a Result from a Result that is of a Failure type", async () => { + const resultValue = result.err>(new Error()); + + const finalResult = await result.fromPromise(resultValue); + + expect(finalResult.isError()).toBeTruthy(); + }); }); - it("Constructs a Result from a Result containing a promise that rejects", async () => { - const resultValue = result.ok(Promise.reject(new NewErrorClass())); + describe("expect", () => { + it("Does not throw if the Result is a Success variant", () => { + const res = result.ok(2); + + expect(() => res.expect("Some Message")).not.toThrow(); + }); - const finalResult = await result.fromPromise(resultValue); + it("Throws with the expected message if called on a Failure type", () => { + const expectedMessage = "Some Message"; + const res = result.err(); - expect(() => finalResult.unwrap()).toThrow(NewErrorClass); + expect(() => res.expect(expectedMessage)).toThrow( + new Error(expectedMessage), + ); + }); }); - it("Constructs a Result from a Result that is of a Failure type", async () => { - const resultValue = result.err>(new Error()); + describe("inspect", () => { + it("Calls the callback function if called on a Success variant", () => { + const spy = jest.fn(); + const res = result.ok(123); + res.inspect((value) => spy(value)); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(123); + }); + + it("Does not call callback function if called on a Failure variant", () => { + const spy = jest.fn(); + const res = result.err(); + + res.inspect((value) => spy(value)); + + expect(spy).not.toHaveBeenCalled(); + }); + + it("Returns the same instance of Result when called with a Success variant", () => { + const spy = jest.fn(); + const res = result.ok(123); + const inspectRes = res.inspect((val) => spy(val)); + + expect(spy).toHaveBeenCalledTimes(1); + expect(inspectRes).toEqual(res); + }); - const finalResult = await result.fromPromise(resultValue); + it("Returns the same instance of Result when called with a Failure variant", () => { + const spy = jest.fn(); + const res = result.err(); + const inspectRes = res.inspect((val) => spy(val)); - expect(finalResult.isError()).toBeTruthy(); + expect(spy).not.toHaveBeenCalled(); + expect(inspectRes).toEqual(res); + }); }); }); From 0a2117f945bbbc63a6829a39e9043d5e843d08be Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 8 Sep 2025 14:26:37 -0700 Subject: [PATCH 2/3] 1.2.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e630eab..a19b620 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dbidwell94/ts-utils", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dbidwell94/ts-utils", - "version": "1.1.0", + "version": "1.2.0", "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", diff --git a/package.json b/package.json index ad6bdb1..ca83f11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dbidwell94/ts-utils", - "version": "1.1.0", + "version": "1.2.0", "description": "A collection of helpful TypeScript utilities with the aim of having limited production dependencies.", "main": "dist/index.js", "files": [ From 9b7dfe39eb9adccb653a007131696a489dacb700 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Mon, 8 Sep 2025 14:30:21 -0700 Subject: [PATCH 3/3] bump minor version and update changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 314fd2a..83e1bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.2.0] + +- Added new utility types to `Option` and `Result` types + - Option + - inspect(callback) -- Calls the provided `callback` if called on + a `Some` variant + - Result + - inspect(callback) -- Calls the provided `callback` if called on + a `Failure` variant + - expect(message) -- Unwraps the inner value if variant is `Success`, + otherwise throws an `Error` with the provided `message` +- Update test structure to be more readable and easier to add tests + ## [1.1.0] - Added new unsafe functions to the `Option` type to return unchecked values.