From a4f301024135f8b0fb046cdb877f44d32919ddb1 Mon Sep 17 00:00:00 2001 From: Michael Yong Date: Thu, 10 Sep 2026 18:06:11 -0700 Subject: [PATCH] Split provider usage footer UI coverage --- plugins/provider-usage/app.test.tsx | 167 ++++++++++++++++------------ 1 file changed, 96 insertions(+), 71 deletions(-) diff --git a/plugins/provider-usage/app.test.tsx b/plugins/provider-usage/app.test.tsx index 409d5ec8a5..4878066fba 100644 --- a/plugins/provider-usage/app.test.tsx +++ b/plugins/provider-usage/app.test.tsx @@ -50,9 +50,13 @@ function threadOnMachine( }; } +const app = await loadPluginApp(() => import("./app")); +const item = app.experimentalSidebarFooterItems[0]; +if (item?.kind !== "disclosure") throw new Error("missing disclosure"); + describe("provider usage footer disclosure", () => { - it("aggregates every machine and keeps machine and provider selection local to the card", async () => { - const fetchMock = vi.fn( + function createFetchMock() { + return vi.fn( async (_input: RequestInfo | URL, _init?: RequestInit) => new Response( JSON.stringify({ @@ -153,39 +157,111 @@ describe("provider usage footer disclosure", () => { }, }), { status: 200, headers: { "content-type": "application/json" } }, - ), + ), ); - vi.stubGlobal("fetch", fetchMock); - const app = await loadPluginApp(() => import("./app")); - const mounted = await mountPluginContentScripts(app, { - pluginId: "provider-usage", - }); - const item = app.experimentalSidebarFooterItems[0]; + } + + it("registers the footer disclosure", () => { expect(item).toMatchObject({ kind: "disclosure", id: "usage", label: "Provider usage", icon: "ChartColumn", }); - if (item?.kind !== "disclosure") throw new Error("missing disclosure"); + }); - await waitFor(() => - expect(fetchMock).toHaveBeenCalledWith( - "/api/v1/plugins/provider-usage/rpc/getUsage", + it("preloads usage and refreshes after an extended focus loss", async () => { + const fetchMock = createFetchMock(); + vi.stubGlobal("fetch", fetchMock); + const mounted = await mountPluginContentScripts(app, { + pluginId: "provider-usage", + }); + try { + await waitFor(() => + expect(fetchMock).toHaveBeenCalledWith( + "/api/v1/plugins/provider-usage/rpc/getUsage", + expect.objectContaining({ + method: "POST", + body: JSON.stringify({ + force: false, + machineIds: null, + maxAgeMs: 30 * 60_000, + }), + }), + ), + ); + + const now = vi.spyOn(Date, "now").mockReturnValue(1_000); + window.dispatchEvent(new Event("blur")); + now.mockReturnValue(5 * 60_000 + 1_001); + const callsBeforeFocus = fetchMock.mock.calls.length; + window.dispatchEvent(new Event("focus")); + await waitFor(() => + expect(fetchMock).toHaveBeenCalledTimes(callsBeforeFocus + 1), + ); + expect(fetchMock.mock.calls.at(-1)?.[1]).toEqual( expect.objectContaining({ - method: "POST", body: JSON.stringify({ force: false, machineIds: null, - maxAgeMs: 30 * 60_000, + maxAgeMs: 5 * 60_000, }), }), - ), - ); + ); + } finally { + await mounted.lifecycle.dispose(); + } + }); + + it("shows disconnected usage and scopes manual refresh to that machine", async () => { + const fetchMock = createFetchMock(); + vi.stubGlobal("fetch", fetchMock); const dismiss = vi.fn(); const slot = renderSlot( item, { dismiss }, + { + context: { threadId: "thread-active" }, + sidebarThreads: { + threads: [threadOnMachine("host-intel", "Intel")], + }, + }, + ); + expect( + await slot.findByText( + "Intel is offline. Usage will refresh when it reconnects.", + ), + ).toBeTruthy(); + fireEvent.click( + slot.getByRole("button", { name: "Collapse provider usage" }), + ); + expect(dismiss).toHaveBeenCalledOnce(); + const reloadButton = slot.getByRole("button", { + name: "Reload provider usage", + }) as HTMLButtonElement; + await waitFor(() => expect(reloadButton.disabled).toBe(false)); + const callsBeforeManualRefresh = fetchMock.mock.calls.length; + fireEvent.click(reloadButton); + await waitFor(() => + expect(fetchMock).toHaveBeenCalledTimes(callsBeforeManualRefresh + 1), + ); + expect(fetchMock.mock.calls.at(-1)?.[1]).toEqual( + expect.objectContaining({ + body: JSON.stringify({ + force: true, + machineIds: ["host-intel"], + maxAgeMs: 0, + }), + }), + ); + }); + + it("aggregates machines and keeps provider selection local to the card", async () => { + const fetchMock = createFetchMock(); + vi.stubGlobal("fetch", fetchMock); + const slot = renderSlot( + item, + { dismiss: vi.fn() }, { context: { threadId: "thread-active" }, sidebarThreads: { @@ -193,7 +269,7 @@ describe("provider usage footer disclosure", () => { }, }, ); - const machinePicker = slot.getByRole("button", { + const machinePicker = await slot.findByRole("button", { name: "Usage machine: M5", }); expect(slot.getByRole("heading", { name: "Codex" })).toBeTruthy(); @@ -201,6 +277,8 @@ describe("provider usage footer disclosure", () => { expect(slot.getByText("97% used")).toBeTruthy(); fireEvent.pointerDown(machinePicker, { button: 0 }); + expect(slot.getByRole("menuitemradio", { name: "M5" })).toBeTruthy(); + expect(slot.getByRole("menuitemradio", { name: "Intel" })).toBeTruthy(); fireEvent.click(slot.getByRole("menuitemradio", { name: "M4" })); const claudeTab = slot.getByRole("tab", { name: "Claude Code" }); const codexTab = slot.getByRole("tab", { name: "Codex" }); @@ -225,58 +303,5 @@ describe("provider usage footer disclosure", () => { expect(slot.getByText("37% used")).toBeTruthy(); fireEvent.keyDown(codexTab, { key: "ArrowLeft" }); expect(claudeTab.getAttribute("aria-selected")).toBe("true"); - - fireEvent.pointerDown( - slot.getByRole("button", { name: "Usage machine: M4" }), - { button: 0 }, - ); - fireEvent.click(slot.getByRole("menuitemradio", { name: "Intel" })); - expect( - slot.getByText( - "Intel is offline. Usage will refresh when it reconnects.", - ), - ).toBeTruthy(); - fireEvent.click( - slot.getByRole("button", { name: "Collapse provider usage" }), - ); - expect(dismiss).toHaveBeenCalledOnce(); - const reloadButton = slot.getByRole("button", { - name: "Reload provider usage", - }) as HTMLButtonElement; - await waitFor(() => expect(reloadButton.disabled).toBe(false)); - const callsBeforeManualRefresh = fetchMock.mock.calls.length; - fireEvent.click(reloadButton); - await waitFor(() => - expect(fetchMock).toHaveBeenCalledTimes(callsBeforeManualRefresh + 1), - ); - expect(fetchMock.mock.calls.at(-1)?.[1]).toEqual( - expect.objectContaining({ - body: JSON.stringify({ - force: true, - machineIds: ["host-intel"], - maxAgeMs: 0, - }), - }), - ); - - const now = vi.spyOn(Date, "now").mockReturnValue(1_000); - window.dispatchEvent(new Event("blur")); - now.mockReturnValue(5 * 60_000 + 1_001); - const callsBeforeFocus = fetchMock.mock.calls.length; - window.dispatchEvent(new Event("focus")); - await waitFor(() => - expect(fetchMock).toHaveBeenCalledTimes(callsBeforeFocus + 1), - ); - expect(fetchMock.mock.calls.at(-1)?.[1]).toEqual( - expect.objectContaining({ - body: JSON.stringify({ - force: false, - machineIds: null, - maxAgeMs: 5 * 60_000, - }), - }), - ); - - await mounted.lifecycle.dispose(); }); });