Spaces:
Paused
Paused
| // @vitest-environment node | |
| import { describe, expect, it, vi } from "vitest"; | |
| import { __liveUpdatesTestUtils } from "./LiveUpdatesProvider"; | |
| import { queryKeys } from "../lib/queryKeys"; | |
| describe("LiveUpdatesProvider issue invalidation", () => { | |
| it("refreshes touched inbox queries for issue activity", () => { | |
| const invalidations: unknown[] = []; | |
| const queryClient = { | |
| invalidateQueries: (input: unknown) => { | |
| invalidations.push(input); | |
| }, | |
| getQueryData: () => undefined, | |
| }; | |
| __liveUpdatesTestUtils.invalidateActivityQueries( | |
| queryClient as never, | |
| "company-1", | |
| { | |
| entityType: "issue", | |
| entityId: "issue-1", | |
| details: null, | |
| }, | |
| ); | |
| expect(invalidations).toContainEqual({ | |
| queryKey: queryKeys.issues.listMineByMe("company-1"), | |
| }); | |
| expect(invalidations).toContainEqual({ | |
| queryKey: queryKeys.issues.listTouchedByMe("company-1"), | |
| }); | |
| expect(invalidations).toContainEqual({ | |
| queryKey: queryKeys.issues.listUnreadTouchedByMe("company-1"), | |
| }); | |
| }); | |
| }); | |
| describe("LiveUpdatesProvider visible issue toast suppression", () => { | |
| it("suppresses activity toasts for the issue page currently in view", () => { | |
| const queryClient = { | |
| getQueryData: (key: unknown) => { | |
| if (JSON.stringify(key) === JSON.stringify(queryKeys.issues.detail("PAP-759"))) { | |
| return { | |
| id: "issue-1", | |
| identifier: "PAP-759", | |
| assigneeAgentId: "agent-1", | |
| }; | |
| } | |
| return undefined; | |
| }, | |
| }; | |
| expect( | |
| __liveUpdatesTestUtils.shouldSuppressActivityToastForVisibleIssue( | |
| queryClient as never, | |
| "/PAP/issues/PAP-759", | |
| { | |
| entityType: "issue", | |
| entityId: "issue-1", | |
| details: { identifier: "PAP-759" }, | |
| }, | |
| { isForegrounded: true }, | |
| ), | |
| ).toBe(true); | |
| expect( | |
| __liveUpdatesTestUtils.shouldSuppressActivityToastForVisibleIssue( | |
| queryClient as never, | |
| "/PAP/issues/PAP-759", | |
| { | |
| entityType: "issue", | |
| entityId: "issue-2", | |
| details: { identifier: "PAP-760" }, | |
| }, | |
| { isForegrounded: true }, | |
| ), | |
| ).toBe(false); | |
| }); | |
| it("suppresses run and agent status toasts for the assignee of the visible issue", () => { | |
| const queryClient = { | |
| getQueryData: (key: unknown) => { | |
| if (JSON.stringify(key) === JSON.stringify(queryKeys.issues.detail("PAP-759"))) { | |
| return { | |
| id: "issue-1", | |
| identifier: "PAP-759", | |
| assigneeAgentId: "agent-1", | |
| }; | |
| } | |
| return undefined; | |
| }, | |
| }; | |
| expect( | |
| __liveUpdatesTestUtils.shouldSuppressRunStatusToastForVisibleIssue( | |
| queryClient as never, | |
| "/PAP/issues/PAP-759", | |
| { | |
| runId: "run-1", | |
| agentId: "agent-1", | |
| }, | |
| { isForegrounded: true }, | |
| ), | |
| ).toBe(true); | |
| expect( | |
| __liveUpdatesTestUtils.shouldSuppressAgentStatusToastForVisibleIssue( | |
| queryClient as never, | |
| "/PAP/issues/PAP-759", | |
| { | |
| agentId: "agent-1", | |
| status: "running", | |
| }, | |
| { isForegrounded: true }, | |
| ), | |
| ).toBe(true); | |
| }); | |
| }); | |
| describe("LiveUpdatesProvider run lifecycle toasts", () => { | |
| it("does not build start or success toasts for agent runs", () => { | |
| const queryClient = { | |
| getQueryData: () => [], | |
| }; | |
| expect( | |
| __liveUpdatesTestUtils.buildAgentStatusToast( | |
| { | |
| agentId: "agent-1", | |
| status: "running", | |
| }, | |
| () => "CodexCoder", | |
| queryClient as never, | |
| "company-1", | |
| ), | |
| ).toBeNull(); | |
| expect( | |
| __liveUpdatesTestUtils.buildRunStatusToast( | |
| { | |
| runId: "run-1", | |
| agentId: "agent-1", | |
| status: "succeeded", | |
| }, | |
| () => "CodexCoder", | |
| ), | |
| ).toBeNull(); | |
| }); | |
| it("still builds failure toasts for agent errors and failed runs", () => { | |
| const queryClient = { | |
| getQueryData: () => [ | |
| { | |
| id: "agent-1", | |
| title: "Software Engineer", | |
| }, | |
| ], | |
| }; | |
| expect( | |
| __liveUpdatesTestUtils.buildAgentStatusToast( | |
| { | |
| agentId: "agent-1", | |
| status: "error", | |
| }, | |
| () => "CodexCoder", | |
| queryClient as never, | |
| "company-1", | |
| ), | |
| ).toMatchObject({ | |
| title: "CodexCoder errored", | |
| body: "Software Engineer", | |
| tone: "error", | |
| }); | |
| expect( | |
| __liveUpdatesTestUtils.buildRunStatusToast( | |
| { | |
| runId: "run-1", | |
| agentId: "agent-1", | |
| status: "failed", | |
| error: "boom", | |
| }, | |
| () => "CodexCoder", | |
| ), | |
| ).toMatchObject({ | |
| title: "CodexCoder run failed", | |
| body: "boom", | |
| tone: "error", | |
| }); | |
| }); | |
| }); | |
| describe("LiveUpdatesProvider socket helpers", () => { | |
| it("waits for the selected company object to catch up before connecting", () => { | |
| expect(__liveUpdatesTestUtils.resolveLiveCompanyId("company-1", null)).toBeNull(); | |
| expect(__liveUpdatesTestUtils.resolveLiveCompanyId("company-1", "company-2")).toBeNull(); | |
| expect(__liveUpdatesTestUtils.resolveLiveCompanyId("company-1", "company-1")).toBe("company-1"); | |
| }); | |
| it("defers close until onopen for sockets that are still connecting", () => { | |
| const socket = { | |
| readyState: 0, | |
| onopen: (() => undefined) as (() => void) | null, | |
| onmessage: (() => undefined) as (() => void) | null, | |
| onerror: (() => undefined) as (() => void) | null, | |
| onclose: (() => undefined) as (() => void) | null, | |
| close: vi.fn(), | |
| }; | |
| __liveUpdatesTestUtils.closeSocketQuietly(socket as never, "provider_unmount"); | |
| expect(socket.close).not.toHaveBeenCalled(); | |
| expect(socket.onmessage).toBeNull(); | |
| expect(socket.onclose).toBeNull(); | |
| expect(socket.onopen).toBeTypeOf("function"); | |
| expect(socket.onerror).toBeTypeOf("function"); | |
| socket.onopen?.(); | |
| expect(socket.close).toHaveBeenCalledWith(1000, "provider_unmount"); | |
| expect(socket.onopen).toBeNull(); | |
| expect(socket.onerror).toBeNull(); | |
| }); | |
| it("closes open sockets immediately without leaving handlers behind", () => { | |
| const socket = { | |
| readyState: 1, | |
| onopen: (() => undefined) as (() => void) | null, | |
| onmessage: (() => undefined) as (() => void) | null, | |
| onerror: (() => undefined) as (() => void) | null, | |
| onclose: (() => undefined) as (() => void) | null, | |
| close: vi.fn(), | |
| }; | |
| __liveUpdatesTestUtils.closeSocketQuietly(socket as never, "stale_connection"); | |
| expect(socket.close).toHaveBeenCalledWith(1000, "stale_connection"); | |
| expect(socket.onopen).toBeNull(); | |
| expect(socket.onmessage).toBeNull(); | |
| expect(socket.onerror).toBeNull(); | |
| expect(socket.onclose).toBeNull(); | |
| }); | |
| }); | |