import { describe, expect, it } from "vitest"; import { agentPointerFeed, moveDurationMs, planPointerTimeline, pointerFrameAt, } from "./agent-pointer"; const payload = { kind: "pointer", screen: { width: 1280, height: 800 }, steps: [{ action: "click", x: 100, y: 100 }], }; describe("agentPointerFeed", () => { it("delivers valid live announcements only to the bot's viewers", () => { const seen: unknown[] = []; const other: unknown[] = []; const stop = agentPointerFeed.subscribe("bot-a", (value) => seen.push(value)); const stopOther = agentPointerFeed.subscribe("bot-b", (value) => other.push(value)); try { const now = Date.parse("2026-01-01T00:00:10Z"); expect(agentPointerFeed.publish("bot-a", payload, "2026-01-01T00:00:05Z", now)).toBe(true); // Replayed history and malformed payloads never move the cursor. expect(agentPointerFeed.publish("bot-a", payload, "2025-12-31T23:00:00Z", now)).toBe(false); expect(agentPointerFeed.publish("bot-a", { ...payload, steps: [] }, undefined, now)).toBe( false, ); expect(agentPointerFeed.publish("bot-a", { kind: "status" }, undefined, now)).toBe(false); expect(seen).toEqual([payload]); expect(other).toEqual([]); } finally { stop(); stopOther(); } expect(agentPointerFeed.publish("bot-a", payload)).toBe(true); expect(seen).toHaveLength(1); }); }); describe("pointer timeline", () => { it("travels, acts in place for keyboard steps, then rests", () => { const segments = planPointerTimeline( { x: 0, y: 0 }, [{ action: "click", x: 300, y: 400 }, { action: "type" }, { action: "move", x: 300, y: 400 }], 1_000, ); expect(segments.map((segment) => segment.to)).toEqual([ { x: 300, y: 400 }, { x: 300, y: 400 }, { x: 300, y: 400 }, ]); const [click, type, move] = segments; expect(click!.arrive - click!.start).toBe(moveDurationMs(500)); expect(type!.arrive).toBe(type!.start); expect(move!.end).toBe(move!.start); const midway = pointerFrameAt(segments, (click!.start + click!.arrive) / 2)!; expect(midway.action).toBeNull(); // The bow lifts the cursor off the straight line between the points. const onLine = { x: 150, y: 200 }; expect(Math.hypot(midway.point.x - onLine.x, midway.point.y - onLine.y)).toBeGreaterThan(10); const clicking = pointerFrameAt(segments, click!.arrive + 1)!; expect(clicking.point).toEqual({ x: 300, y: 400 }); expect(clicking.action?.kind).toBe("click"); const typing = pointerFrameAt(segments, type!.arrive + 10)!; expect(typing.action?.kind).toBe("type"); expect(pointerFrameAt(segments, move!.end + 1)).toEqual({ point: { x: 300, y: 400 }, action: null, done: true, }); }); it("jumps straight to the target when motion is reduced", () => { const segments = planPointerTimeline({ x: 0, y: 0 }, [{ action: "move", x: 50, y: 60 }], 0); expect(pointerFrameAt(segments, 1, true)?.point).toEqual({ x: 50, y: 60 }); }); it("bounds travel time so long and short moves both feel responsive", () => { expect(moveDurationMs(0)).toBe(0); expect(moveDurationMs(5)).toBe(180); expect(moveDurationMs(10_000)).toBe(650); }); });