import { describe, it, expect } from "vitest"; import { invariant, assertNever, InvariantError, } from "@/lib/invariant"; describe("InvariantError", () => { it("is an instance of Error", () => { const error = new InvariantError("test message"); expect(error).toBeInstanceOf(Error); }); it("has the correct name", () => { const error = new InvariantError("test message"); expect(error.name).toBe("InvariantError"); }); it("preserves the message", () => { const error = new InvariantError("something went wrong"); expect(error.message).toBe("something went wrong"); }); it("has a stack trace", () => { const error = new InvariantError("test"); expect(error.stack).toBeDefined(); }); }); describe("invariant", () => { it("does not throw when condition is true", () => { expect(() => invariant(true, "should not throw")).not.toThrow(); }); it("does not throw for truthy values", () => { expect(() => invariant(1, "truthy number")).not.toThrow(); expect(() => invariant("non-empty", "truthy string")).not.toThrow(); expect(() => invariant({}, "empty object is truthy")).not.toThrow(); expect(() => invariant([], "empty array is truthy")).not.toThrow(); }); it("throws InvariantError when condition is false", () => { expect(() => invariant(false, "invariant violated")).toThrow( InvariantError ); }); it("throws InvariantError for falsy values", () => { expect(() => invariant(null, "null check")).toThrow(InvariantError); expect(() => invariant(undefined, "undefined check")).toThrow( InvariantError ); expect(() => invariant(0, "zero check")).toThrow(InvariantError); expect(() => invariant("", "empty string check")).toThrow(InvariantError); }); it("includes the message in the error", () => { try { invariant(false, "Expected value to be defined"); } catch (e) { expect(e).toBeInstanceOf(InvariantError); expect((e as InvariantError).message).toBe("Expected value to be defined"); } }); it("works as a type guard narrowing null/undefined", () => { const maybeValue: string | null = "hello"; invariant(maybeValue !== null, "value should not be null"); // TypeScript should now know maybeValue is string const length: number = maybeValue.length; expect(length).toBe(5); }); it("works with a lazy message function", () => { const expensiveMessage = () => `Computed at ${Date.now()}`; expect(() => invariant(false, expensiveMessage)).toThrow(InvariantError); }); it("only evaluates lazy message when condition fails", () => { let called = false; const lazyMessage = () => { called = true; return "computed message"; }; invariant(true, lazyMessage); expect(called).toBe(false); try { invariant(false, lazyMessage); } catch { // expected } expect(called).toBe(true); }); }); describe("assertNever", () => { it("throws InvariantError when called", () => { // We need to force TypeScript to let us call this const invalid = "unexpected" as never; expect(() => assertNever(invalid)).toThrow(InvariantError); }); it("includes the unexpected value in the error message", () => { const invalid = "unexpected_value" as never; try { assertNever(invalid); } catch (e) { expect(e).toBeInstanceOf(InvariantError); expect((e as InvariantError).message).toContain("unexpected_value"); } }); it("handles object values in error message", () => { const invalid = { type: "unknown" } as never; try { assertNever(invalid); } catch (e) { expect(e).toBeInstanceOf(InvariantError); expect((e as InvariantError).message).toContain("unknown"); } }); it("is useful for exhaustive switch statements", () => { type Status = "pending" | "complete"; function handleStatus(status: Status): string { switch (status) { case "pending": return "waiting"; case "complete": return "done"; default: // If we add a new status, TypeScript will error here return assertNever(status); } } expect(handleStatus("pending")).toBe("waiting"); expect(handleStatus("complete")).toBe("done"); }); });