repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/afk/__tests__/widget.test.ts
Rawimport { visibleWidth } from "@earendil-works/pi-tui";
import { describe, expect, it, vi } from "vitest";
import { createAfkWidget } from "../src/widget.ts";
describe("AFK widget", () => {
it("preserves blink cadence across invalidation", () => {
vi.useFakeTimers();
const widget = createAfkWidget(
{ requestRender() {} } as never,
{ fg: (_color: string, text: string) => text } as never,
() => false,
);
try {
for (const _tick of [1, 2]) {
vi.advanceTimersByTime(700);
expect(widget.render(80).join("\n")).toContain("o.o");
}
widget.invalidate();
vi.advanceTimersByTime(700);
expect(widget.render(80).join("\n")).toContain("-.-");
vi.advanceTimersByTime(700);
expect(widget.render(80).join("\n")).toContain("o.o");
} finally {
widget.dispose();
vi.useRealTimers();
}
});
it("resumes animation when new work wakes the cat", () => {
vi.useFakeTimers();
let napping = true;
const requestRender = vi.fn();
const widget = createAfkWidget(
{ requestRender } as never,
{ fg: (_color: string, text: string) => text } as never,
() => napping,
);
try {
expect(widget.render(80).join("\n")).toContain("zzz");
vi.advanceTimersByTime(5_600);
napping = false;
widget.invalidate();
expect(widget.render(80).join("\n")).toContain("o.o");
vi.advanceTimersByTime(2_100);
expect(widget.render(80).join("\n")).toContain("-.-");
} finally {
widget.dispose();
vi.useRealTimers();
}
});
it("redraws only blink transitions and has no timer while napping", () => {
vi.useFakeTimers();
let napping = false;
const requestRender = vi.fn();
const widget = createAfkWidget(
{ requestRender } as never,
{ fg: (_color: string, text: string) => text } as never,
() => napping,
);
try {
vi.advanceTimersByTime(1_400);
expect(requestRender).not.toHaveBeenCalled();
vi.advanceTimersByTime(1_400);
expect(requestRender).toHaveBeenCalledTimes(2);
napping = true;
widget.invalidate();
expect(vi.getTimerCount()).toBe(0);
const asleepRenders = requestRender.mock.calls.length;
vi.advanceTimersByTime(60_000);
expect(requestRender).toHaveBeenCalledTimes(asleepRenders);
napping = false;
widget.invalidate();
expect(vi.getTimerCount()).toBe(1);
widget.invalidate();
expect(vi.getTimerCount()).toBe(1);
} finally {
widget.dispose();
vi.useRealTimers();
}
});
it("blinks, naps, fits widths, bounds frames, and disposes idempotently", () => {
vi.useFakeTimers();
try {
const tui = { requestRender: vi.fn() };
let napping = false;
const widget = createAfkWidget(
tui as never,
{
fg: (_color: string, text: string) => `\u001b[36m${text}\u001b[0m`,
} as never,
() => napping,
);
for (const width of [0, 1, 7, 12, 80])
expect(
widget.render(width).every((line) => visibleWidth(line) <= width),
).toBe(true);
expect(widget.render(80).join("\\n")).toContain("o.o");
vi.advanceTimersByTime(2_100);
expect(widget.render(80).join("\\n")).toContain("-.-");
vi.advanceTimersByTime(700 * 1_001);
expect(widget.render(80).join("\\n")).toContain("o.o");
napping = true;
expect(widget.render(80).join("\\n")).toContain("zzz");
const renders = tui.requestRender.mock.calls.length;
widget.dispose();
widget.dispose();
vi.advanceTimersByTime(1_400);
expect(tui.requestRender).toHaveBeenCalledTimes(renders);
expect(vi.getTimerCount()).toBe(0);
} finally {
vi.useRealTimers();
}
});
});