diff --git a/ui/src/ui/app-chat.test.ts b/ui/src/ui/app-chat.test.ts index 369495d7e4a..6c6459d1b05 100644 --- a/ui/src/ui/app-chat.test.ts +++ b/ui/src/ui/app-chat.test.ts @@ -133,12 +133,15 @@ function row(key: string, overrides?: Partial): GatewaySessio } function createDeferred() { - let resolve!: (value: T) => void; - let reject!: (reason?: unknown) => void; + let resolve: ((value: T) => void) | undefined; + let reject: ((reason?: unknown) => void) | undefined; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + if (!resolve || !reject) { + throw new Error("Expected deferred callbacks to be initialized"); + } return { promise, resolve, reject }; } diff --git a/ui/src/ui/app-lifecycle-connect.node.test.ts b/ui/src/ui/app-lifecycle-connect.node.test.ts index c49adc81a5b..e77f56f5d47 100644 --- a/ui/src/ui/app-lifecycle-connect.node.test.ts +++ b/ui/src/ui/app-lifecycle-connect.node.test.ts @@ -41,6 +41,17 @@ vi.mock("./app-scroll.ts", () => ({ import { handleConnected } from "./app-lifecycle.ts"; +function createDeferred() { + let resolve: (() => void) | undefined; + const promise = new Promise((res) => { + resolve = res; + }); + if (!resolve) { + throw new Error("Expected bootstrap deferred resolver to be initialized"); + } + return { promise, resolve }; +} + function createHost() { return { basePath: "", @@ -77,30 +88,22 @@ describe("handleConnected", () => { }); it("waits for bootstrap load before first gateway connect", async () => { - let resolveBootstrap!: () => void; - loadBootstrapMock.mockReturnValueOnce( - new Promise((resolve) => { - resolveBootstrap = resolve; - }), - ); + const bootstrap = createDeferred(); + loadBootstrapMock.mockReturnValueOnce(bootstrap.promise); connectGatewayMock.mockReset(); const host = createHost(); handleConnected(host as never); expect(connectGatewayMock).not.toHaveBeenCalled(); - resolveBootstrap(); + bootstrap.resolve(); await Promise.resolve(); expect(connectGatewayMock).toHaveBeenCalledTimes(1); }); it("skips deferred connect when disconnected before bootstrap resolves", async () => { - let resolveBootstrap!: () => void; - loadBootstrapMock.mockReturnValueOnce( - new Promise((resolve) => { - resolveBootstrap = resolve; - }), - ); + const bootstrap = createDeferred(); + loadBootstrapMock.mockReturnValueOnce(bootstrap.promise); connectGatewayMock.mockReset(); const host = createHost(); @@ -108,7 +111,7 @@ describe("handleConnected", () => { expect(connectGatewayMock).not.toHaveBeenCalled(); host.connectGeneration += 1; - resolveBootstrap(); + bootstrap.resolve(); await Promise.resolve(); expect(connectGatewayMock).not.toHaveBeenCalled(); diff --git a/ui/src/ui/app-settings.refresh-active-tab.node.test.ts b/ui/src/ui/app-settings.refresh-active-tab.node.test.ts index 8038adb5af2..d05829b9a3a 100644 --- a/ui/src/ui/app-settings.refresh-active-tab.node.test.ts +++ b/ui/src/ui/app-settings.refresh-active-tab.node.test.ts @@ -3,6 +3,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; type CronRunsLoadStatus = "ok" | "error" | "skipped"; +function createDeferred() { + let resolve: ((value: T | PromiseLike) => void) | undefined; + const promise = new Promise((res) => { + resolve = res; + }); + if (!resolve) { + throw new Error("Expected deferred resolver to be initialized"); + } + return { promise, resolve }; +} + const mocks = vi.hoisted(() => ({ refreshChatMock: vi.fn(async () => {}), scheduleChatScrollMock: vi.fn(), @@ -196,12 +207,8 @@ describe("refreshActiveTab", () => { it("records tab visible timing without waiting for the tab refresh RPC", async () => { const host = createHost(); host.tab = "chat"; - let resolveSessions!: () => void; - mocks.loadSessionsMock.mockReturnValueOnce( - new Promise((resolve) => { - resolveSessions = resolve; - }), - ); + const sessions = createDeferred(); + mocks.loadSessionsMock.mockReturnValueOnce(sessions.promise); setTab(host as never, "sessions"); @@ -221,7 +228,7 @@ describe("refreshActiveTab", () => { ); }); - resolveSessions(); + sessions.resolve(); }); it("does not wait for secondary overview refreshes before resolving", async () => { @@ -244,12 +251,8 @@ describe("refreshActiveTab", () => { it("does not wait for config schema before resolving config tab refresh", async () => { const host = createHost(); host.tab = "config"; - let resolveSchema!: () => void; - mocks.loadConfigSchemaMock.mockReturnValueOnce( - new Promise((resolve) => { - resolveSchema = resolve; - }), - ); + const schema = createDeferred(); + mocks.loadConfigSchemaMock.mockReturnValueOnce(schema.promise); const refresh = refreshActiveTab(host as never); const outcome = await Promise.race([ @@ -262,7 +265,7 @@ describe("refreshActiveTab", () => { expect(mocks.loadConfigMock).toHaveBeenCalledOnce(); expect(host.requestUpdate).not.toHaveBeenCalled(); - resolveSchema(); + schema.resolve(); await vi.waitFor(() => { expect(host.requestUpdate).toHaveBeenCalledOnce(); @@ -272,18 +275,12 @@ describe("refreshActiveTab", () => { it("renders channels from the cheap snapshot before starting slow probes", async () => { const host = createHost(); host.tab = "channels"; - let resolveSchema!: () => void; - let resolveProbe!: () => void; - mocks.loadConfigSchemaMock.mockReturnValueOnce( - new Promise((resolve) => { - resolveSchema = resolve; - }), - ); + const schema = createDeferred(); + const channelProbe = createDeferred(); + mocks.loadConfigSchemaMock.mockReturnValueOnce(schema.promise); mocks.loadChannelsMock.mockImplementation(async (_host, probe) => { if (probe) { - await new Promise((resolve) => { - resolveProbe = resolve; - }); + await channelProbe.promise; } }); @@ -298,8 +295,8 @@ describe("refreshActiveTab", () => { expect(mocks.loadConfigMock).toHaveBeenCalledOnce(); expect(host.requestUpdate).not.toHaveBeenCalled(); - resolveSchema(); - resolveProbe(); + schema.resolve(); + channelProbe.resolve(); await vi.waitFor(() => { expect(host.requestUpdate).toHaveBeenCalledTimes(2); @@ -309,16 +306,12 @@ describe("refreshActiveTab", () => { it("records overview secondary refresh duration and aggregate status", async () => { const host = createHost(); host.tab = "overview"; - let resolveUsage!: () => void; - mocks.loadUsageMock.mockReturnValueOnce( - new Promise((resolve) => { - resolveUsage = resolve; - }), - ); + const usage = createDeferred(); + mocks.loadUsageMock.mockReturnValueOnce(usage.promise); mocks.loadSkillsMock.mockRejectedValueOnce(new Error("skills failed")); await refreshActiveTab(host as never); - resolveUsage(); + usage.resolve(); await vi.waitFor(() => { expect(host.eventLogBuffer).toEqual( @@ -401,16 +394,12 @@ describe("refreshActiveTab", () => { it("does not record stale cron run timing after leaving the cron tab", async () => { const host = createHost(); host.tab = "cron"; - let resolveRuns!: () => void; - mocks.loadCronRunsMock.mockReturnValueOnce( - new Promise<"ok">((resolve) => { - resolveRuns = () => resolve("ok"); - }), - ); + const runs = createDeferred<"ok">(); + mocks.loadCronRunsMock.mockReturnValueOnce(runs.promise); await refreshActiveTab(host as never); host.tab = "chat"; - resolveRuns(); + runs.resolve("ok"); await Promise.resolve(); expect(host.eventLogBuffer).not.toEqual( diff --git a/ui/src/ui/gateway.node.test.ts b/ui/src/ui/gateway.node.test.ts index 0d31467b194..dd40cc789a1 100644 --- a/ui/src/ui/gateway.node.test.ts +++ b/ui/src/ui/gateway.node.test.ts @@ -27,6 +27,17 @@ type HandlerMap = { type MockWebSocketHandler = (ev?: { code?: number; data?: string; reason?: string }) => void; +function createDeferred() { + let resolve: ((value: T) => void) | undefined; + const promise = new Promise((res) => { + resolve = res; + }); + if (!resolve) { + throw new Error("Expected deferred resolver to be initialized"); + } + return { promise, resolve }; +} + class MockWebSocket { static OPEN = 1; @@ -556,13 +567,8 @@ describe("GatewayBrowserClient", () => { it("does not send stale connect frames on a replacement socket", async () => { vi.useFakeTimers(); - let resolveIdentity!: (identity: DeviceIdentity) => void; - loadOrCreateDeviceIdentityMock.mockImplementationOnce( - () => - new Promise((resolve) => { - resolveIdentity = resolve; - }), - ); + const identity = createDeferred(); + loadOrCreateDeviceIdentityMock.mockImplementationOnce(() => identity.promise); const client = new GatewayBrowserClient({ url: "ws://127.0.0.1:18789", @@ -585,7 +591,7 @@ describe("GatewayBrowserClient", () => { const secondWs = getLatestWebSocket(); expect(secondWs).not.toBe(firstWs); - resolveIdentity({ + identity.resolve({ deviceId: "device-1", privateKey: "private-key", // pragma: allowlist secret publicKey: "public-key", // pragma: allowlist secret