283 lines · 9.1 KB · TypeScript
RawDownload
import { describe, expect, it, vi } from "vite-plus/test"; vi.mock("@expo/ui/swift-ui", () => ({  HStack: "HStack",  Image: "Image",  Spacer: "Spacer",  Text: "Text",  VStack: "VStack",  ZStack: "ZStack",})); vi.mock("@expo/ui/swift-ui/modifiers", () => ({  font: (value: unknown) => value,  foregroundStyle: (value: unknown) => value,  frame: (value: unknown) => value,  layoutPriority: (value: unknown) => value,  lineLimit: (value: unknown) => value,  padding: (value: unknown) => value,  resizable: (value: unknown) => value,  widgetURL: (value: unknown) => ({ widgetURL: value }),})); vi.mock("expo-widgets", () => ({  createLiveActivity: vi.fn((name: string, layout: unknown) => ({ layout, name })),})); import {  AgentActivity,  type AgentActivityProps,  type AgentActivityRowProps,} from "./AgentActivity"; function makeRow(overrides: Partial<AgentActivityRowProps>): AgentActivityRowProps {  return {    environmentId: "env-1",    threadId: "thread-1",    projectTitle: "Project",    threadTitle: "Thread",    modelTitle: "gpt-5.4",    phase: "running",    status: "Working",    updatedAt: "2026-05-25T13:07:00.000Z",    deepLink: "/threads/env-1/thread-1",    ...overrides,  };} const props = {  title: "T3 Code",  subtitle: "Agent work in progress",  activeCount: 1,  updatedAt: "2026-05-25T13:07:00.000Z",  activities: [],} satisfies AgentActivityProps; const environment = {  colorScheme: "dark",  isLuminanceReduced: false,} as const; const lightEnvironment = {  colorScheme: "light",  isLuminanceReduced: false,} as const; describe("AgentActivity widget layout", () => {  it("tints each row by its own phase using the web sidebar's dark palette", () => {    const layout = AgentActivity(      {        ...props,        activeCount: 2,        activities: [          makeRow({}),          makeRow({ threadId: "thread-2", phase: "waiting_for_approval", status: "Approval" }),        ],      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner).toContain("#7dd3fc"); // sky-300: running    expect(banner).toContain("#fcd34d"); // amber-300: waiting_for_approval  });   it("switches to the web sidebar's light palette when the scheme is light", () => {    // macOS (iPhone Mirroring / Mac notification center) renders the activity    // on a light background; the dark-material palette is illegible there.    const layout = AgentActivity(      {        ...props,        activeCount: 2,        activities: [          makeRow({}),          makeRow({ threadId: "thread-2", phase: "waiting_for_approval", status: "Approval" }),        ],      },      lightEnvironment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner).toContain("#0284c7"); // sky-600: running    expect(banner).toContain("#d97706"); // amber-600: waiting_for_approval    expect(banner).not.toContain("#7dd3fc");    expect(banner).not.toContain("#fcd34d");  });   it("orders rows attention-first in the banner", () => {    const layout = AgentActivity(      {        ...props,        activeCount: 2,        activities: [          makeRow({ threadTitle: "Working thread" }),          makeRow({            threadId: "thread-2",            threadTitle: "Blocked thread",            phase: "waiting_for_approval",            status: "Approval",          }),        ],      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner.indexOf("Blocked thread")).toBeGreaterThan(-1);    expect(banner.indexOf("Blocked thread")).toBeLessThan(banner.indexOf("Working thread"));  });   it("summarizes the attention count in the banner header", () => {    const layout = AgentActivity(      {        ...props,        activeCount: 3,        activities: [          makeRow({}),          makeRow({ threadId: "thread-2", phase: "waiting_for_input", status: "Input" }),        ],      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner).toContain("3 active agents");    expect(banner).toContain("1 needs attention");  });   it("uses the attention tint for the compact presentations when a row needs input", () => {    const layout = AgentActivity(      {        ...props,        activeCount: 2,        activities: [          makeRow({}),          makeRow({ threadId: "thread-2", phase: "waiting_for_input", status: "Input" }),        ],      },      environment as never,    );    expect(JSON.stringify(layout.compactLeading)).toContain("#a5b4fc"); // indigo-300    expect(JSON.stringify(layout.compactTrailing)).toContain("Input");    expect(JSON.stringify(layout.minimal)).toContain("#a5b4fc");  });   it("deep links the banner to the row that needs attention", () => {    const layout = AgentActivity(      {        ...props,        activeCount: 2,        activities: [          makeRow({}),          makeRow({            threadId: "thread-2",            phase: "waiting_for_approval",            status: "Approval",            deepLink: "/threads/env-1/thread-2",          }),        ],      },      environment as never,    );    expect(JSON.stringify(layout.banner)).toContain(      '"widgetURL":"t3code://threads/env-1/thread-2"',    );  });   it("deep links the banner to the first row when nothing needs attention", () => {    const layout = AgentActivity({ ...props, activities: [makeRow({})] }, environment as never);    expect(JSON.stringify(layout.banner)).toContain(      '"widgetURL":"t3code://threads/env-1/thread-1"',    );  });   it("omits the deep link for unsafe paths and empty aggregates", () => {    expect(JSON.stringify(AgentActivity(props, environment as never))).not.toContain("widgetURL");    expect(      JSON.stringify(        AgentActivity(          { ...props, activities: [makeRow({ deepLink: "//evil.example" })] },          environment as never,        ),      ),    ).not.toContain("widgetURL");  });   it("leads with the outcome instead of a zero count when nothing is active", () => {    const layout = AgentActivity(      {        ...props,        subtitle: "Agent work completed",        activeCount: 0,        activities: [makeRow({ phase: "completed", status: "Done" })],      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner).toContain("Agent work completed");    expect(banner).not.toContain("0 active");    expect(banner).toContain("#6ee7b7"); // emerald-300 header tint    expect(JSON.stringify(layout.compactTrailing)).toContain("Done");    expect(JSON.stringify(layout.compactTrailing)).not.toContain("0 active");    expect(JSON.stringify(layout.expandedLeading)).toContain("Done");    expect(JSON.stringify(layout.minimal)).toContain("checkmark.circle.fill");    expect(JSON.stringify(layout.bannerSmall)).toContain("Done");  });   it("reads Failed when the finished work ended in failure", () => {    const layout = AgentActivity(      {        ...props,        subtitle: "Agent work failed",        activeCount: 0,        activities: [makeRow({ phase: "failed", status: "Failed" })],      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner).toContain("Agent work failed");    expect(banner).toContain("#fca5a5"); // red-300 header tint    expect(JSON.stringify(layout.compactTrailing)).toContain("Failed");    expect(JSON.stringify(layout.expandedLeading)).toContain("Failed");    expect(JSON.stringify(layout.minimal)).toContain("xmark.octagon.fill");  });   it("lets a failure dominate mixed finished outcomes across every presentation", () => {    const layout = AgentActivity(      {        ...props,        // The server subtitle keys off the newest terminal row (completed        // here); the layout must still read Failed everywhere so the header        // text never disagrees with the tint, count slots, or minimal glyph.        subtitle: "Agent work completed",        activeCount: 0,        activities: [          makeRow({ phase: "completed", status: "Done" }),          makeRow({ threadId: "thread-2", phase: "failed", status: "Failed" }),        ],      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    expect(banner).toContain("Agent work failed");    expect(banner).not.toContain("Agent work completed");    expect(banner).toContain("#fca5a5"); // red-300 header tint    expect(JSON.stringify(layout.compactTrailing)).toContain("Failed");    expect(JSON.stringify(layout.expandedLeading)).toContain("Failed");    expect(JSON.stringify(layout.minimal)).toContain("xmark.octagon.fill");  });   it("renders up to five rows in the banner", () => {    const layout = AgentActivity(      {        ...props,        activeCount: 6,        activities: [1, 2, 3, 4, 5, 6].map((n) =>          makeRow({ threadId: `t${n}`, threadTitle: `Thread ${n}` }),        ),      },      environment as never,    );    const banner = JSON.stringify(layout.banner);    for (const visible of [1, 2, 3, 4, 5]) {      expect(banner).toContain(`Thread ${visible}`);    }    expect(banner).not.toContain("Thread 6");  });});