288 lines
7.6 KiB
TypeScript
288 lines
7.6 KiB
TypeScript
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<T extends Record<string, unknown> = Record<string, unknown>> = import("@paperclover/console/Spinner").Spinner<T>;
|
|
|
|
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"]
|
|
});
|
|
}
|
|
});
|