import { beforeEach, expect, test } from "bun:test"; import { reset, expectCalls, getWidgets } from "./test/widget-mock.ts"; const { Spinner, defaultSpinnerOptions, withSpinner } = await import("@paperclover/console/Spinner"); type Spinner = Record> = import("@paperclover/console/Spinner").Spinner; beforeEach(reset); test("default options and rendering", () => { const spinner = new Spinner("spin"); expect(spinner.props).toEqual({}); expect(spinner.fps).toEqual(defaultSpinnerOptions.fps); expect(spinner.frames).toEqual(defaultSpinnerOptions.frames); expect(defaultSpinnerOptions.frames).toHaveLength(10); let now = 0; for (let i = 0; i < spinner.frames.length; i++) { expect(spinner.format(now)).toEqual(`\x1b[94m${spinner.frames[i]}\x1b[39m spin`); now += 1000 / spinner.fps; } spinner.text = "something"; expectCalls(spinner, { redraws: 1, }); }); test("custom options apply correctly", () => { const customFrames = ['A', 'B', 'C']; const customFps = 24; const spinner = new Spinner({ text: "custom spinner", frames: customFrames, fps: customFps }); expect(spinner.frames).toEqual(customFrames); expect(spinner.fps).toEqual(customFps); expect(spinner.text).toEqual("custom spinner"); let now = 0; for (let i = 0; i < spinner.frames.length; i++) { expect(spinner.format(now)).toEqual(`\x1b[94m${spinner.frames[i]}\x1b[39m custom spinner`); now += 1000 / spinner.fps; } }); test("custom color options", () => { // Test with named color const redSpinner = new Spinner({ text: "red spinner", color: "red" }); expect(redSpinner.format(0)).toEqual(`\x1b[31m${redSpinner.frames[0]}\x1b[39m red spinner`); // Test with hex color const hexSpinner = new Spinner({ text: "hex spinner", color: "#ff00ff" }); expect(hexSpinner.format(0)).toContain(hexSpinner.frames[0]); expect(hexSpinner.format(0)).toContain("hex spinner"); // Test with rgb array const rgbSpinner = new Spinner({ text: "rgb spinner", color: [255, 0, 255] }); expect(rgbSpinner.format(0)).toContain(rgbSpinner.frames[0]); expect(rgbSpinner.format(0)).toContain("rgb spinner"); // Test with color disabled const noColorSpinner = new Spinner({ text: "no color", color: false }); expect(noColorSpinner.format(0)).toEqual(`${noColorSpinner.frames[0]} no color`); }); test("invalid color throws error", () => { expect(() => { new Spinner({ text: "invalid color", color: "invalidColor" as any }); }).toThrow("Invalid color: invalidColor"); }); test("text getter and setter", () => { const spinner = new Spinner("initial text"); expect(spinner.text).toEqual("initial text"); spinner.text = "updated text"; expect(spinner.text).toEqual("updated text"); expectCalls(spinner, { redraws: 1 }); }); test("function for text field and props getter and setter", () => { const textFn = (props: { count: number }) => `Items: ${props.count}`; const spinner = new Spinner<{ count: number; newProp?: string }>({ text: textFn, props: { count: 5 } }); expect(spinner.text).toEqual("Items: 5"); expect(spinner.props).toEqual({ count: 5 }); spinner.props = { count: 10 }; expect(spinner.text).toEqual("Items: 10"); expect(spinner.props).toEqual({ count: 10 }); expectCalls(spinner, { redraws: 1 }); // Adding new props should keep existing ones spinner.props = { newProp: "value" }; expect(spinner.props).toEqual({ count: 10, newProp: "value" }); expectCalls(spinner, { redraws: 2 }); }); test("update method with string", () => { const spinner = new Spinner("initial"); spinner.update("updated via update"); expect(spinner.text).toEqual("updated via update"); expectCalls(spinner, { redraws: 1 }); }); test("update method with props", () => { const textFn = (props: { count: number, name?: string }) => `${props.name || 'Items'}: ${props.count}`; const spinner = new Spinner<{ count: number, name?: string }>({ text: textFn, props: { count: 0 } }); expect(spinner.text).toEqual("Items: 0"); spinner.update({ count: 5 }); expect(spinner.text).toEqual("Items: 5"); spinner.update({ name: "Products" }); expect(spinner.text).toEqual("Products: 5"); expectCalls(spinner, { redraws: 2 }); }); test("success method", () => { const spinner = new Spinner("working"); spinner.success(); expectCalls(spinner, { successCalls: ["working"] }); const spinner2 = new Spinner("working"); spinner2.success("completed"); expectCalls(spinner2, { successCalls: ["completed"] }); }); test("error method", () => { const spinner = new Spinner("working"); spinner.error(); expectCalls(spinner, { errorCalls: ["working"] }); const spinner2 = new Spinner("working"); spinner2.error("failed"); expectCalls(spinner2, { errorCalls: ["failed"] }); const error = new Error("Something went wrong"); const spinner3 = new Spinner("working"); spinner3.error(error); expectCalls(spinner3, { errorCalls: [error] }); }); test("withSpinner function", async () => { const expectedResult = Symbol("unique"); let spin!: Spinner; const result = await withSpinner("Loading data", async (spinner) => { spin = spinner; expect(spinner.text).toEqual("Loading data"); return expectedResult; }); expect(result).toEqual(expectedResult); expectCalls(spin, { successCalls: ["Completed"], }); }); test("withSpinner function with custom success text", async () => { let spin!: Spinner; const result = await withSpinner({ text: "Loading", successText: "Data loaded successfully" }, async (spinner) => { spin = spinner; return "data"; }); expect(result).toEqual("data"); expectCalls(spin, { successCalls: ["Data loaded successfully"], }); }); test("withSpinner function with success text function", async () => { let spin!: Spinner; const result = await withSpinner({ text: "Loading", successText: (data: number[]) => `Loaded ${data.length} items` }, async (spinner) => { spin = spinner; return [1, 2, 3]; }); expect(result).toEqual([1, 2, 3]); expectCalls(spin, { successCalls: ["Loaded 3 items"], }); }); test("withSpinner function with error", async () => { const error = new Error("Failed to load data"); let spin!: Spinner; try { await withSpinner({ text: "Loading", failureText: "Could not load data" }, async (spinner) => { spin = spinner; throw error; }); expect.unreachable(); } catch (e) { expect(e).toEqual(error); expectCalls(spin, { errorCalls: ["Could not load data"] }); } }); test("withSpinner function with error text function", async () => { const error = new Error("Network error"); let spin!: Spinner; try { await withSpinner({ text: "Loading", failureText: (err) => `Error: ${err.message}` }, async (spinner) => { spin = spinner; throw error; }); expect.unreachable(); } catch (e) { expect(e).toEqual(error); expectCalls(spin, { errorCalls: ["Error: Network error"] }); } });