Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/afk/__tests__/widget.test.ts

Raw
import { 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();
		}
	});
});