diff --git a/desktop/frontend-next/src/ui/panels/Extensions.progress.interaction.test.tsx b/desktop/frontend-next/src/ui/panels/Extensions.progress.interaction.test.tsx new file mode 100644 index 0000000000..5e40ebcda9 --- /dev/null +++ b/desktop/frontend-next/src/ui/panels/Extensions.progress.interaction.test.tsx @@ -0,0 +1,81 @@ +// @vitest-environment jsdom +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import type { ExtensionSurface } from "../../port/wire"; +import { Extensions } from "./Extensions"; + +afterEach(cleanup); + +const panel = (title: string | undefined, progress: number | undefined): ExtensionSurface => ({ + pluginId: "sync-watch", surfaceId: "watch", sessionId: "sess-panel", generation: 7, kind: "panel", + panel: { + title, text: "Watching workspace", progress, + fields: [{ key: "branch", value: "studio" }], + actions: [{ actionId: "stop", label: "Stop sync" }], + }, +}); + +describe("standing extension panel progress", () => { + it.each([0, 0.4, 1])("names titled progress at %s from the visible panel identity", (progress) => { + const ui = render(); + const bar = ui.getByRole("progressbar", { name: "Workspace sync" }); + expect(bar.getAttribute("aria-valuenow")).toBe(String(Math.round(progress * 100))); + expect((bar.firstElementChild as HTMLElement).style.width).toBe(`${progress * 100}%`); + expect(ui.getByText("Workspace sync")).toBeTruthy(); + }); + + it.each([0, 0.4, 1])("names untitled progress at %s from the visible plugin identity", (progress) => { + const ui = render(); + const bar = ui.getByRole("progressbar", { name: "sync-watch" }); + expect(bar.getAttribute("aria-valuenow")).toBe(String(Math.round(progress * 100))); + expect(ui.getByText("sync-watch")).toBeTruthy(); + }); + + it("updates the accessible identity and value when the same surface is republished", () => { + const invoke = vi.fn(); + const ui = render(); + ui.rerender(); + expect(ui.queryByRole("progressbar", { name: "Workspace sync" })).toBeNull(); + expect(ui.getByRole("progressbar", { name: "Index rebuild" }).getAttribute("aria-valuenow")).toBe("70"); + ui.rerender(); + expect(ui.getByRole("progressbar", { name: "sync-watch" }).getAttribute("aria-valuenow")).toBe("100"); + }); + + it("keeps fields and the canonical action available by keyboard", async () => { + const invoke = vi.fn(); + const ui = render(); + expect(ui.getByText("Watching workspace")).toBeTruthy(); + expect(ui.getByText("branch").tagName).toBe("DT"); + expect(ui.getByText("studio").tagName).toBe("DD"); + const user = userEvent.setup(); + await user.tab(); + expect(document.activeElement).toBe(ui.getByRole("button", { name: "Stop sync" })); + await user.keyboard("{Enter}"); + expect(invoke.mock.calls).toEqual([["/sync-watch:stop"]]); + }); + + it("does not invent progress when the published panel has none", () => { + const ui = render(); + expect(ui.queryByRole("progressbar")).toBeNull(); + expect(ui.getByRole("button", { name: "Stop sync" })).toBeTruthy(); + }); + + it("keeps composed views and their action invocation beside standing panels", async () => { + const invoke = vi.fn(); + const view: ExtensionSurface = { + pluginId: "task-watch", surfaceId: "summary", kind: "view", + view: { body: [{ kind: "text", value: "Task summary" }, { kind: "button", label: "Inspect task", actionId: "/task-watch:inspect" }] }, + }; + const ui = render(); + expect(ui.getByText("Task summary")).toBeTruthy(); + expect(ui.getAllByRole("progressbar")).toHaveLength(1); + await userEvent.setup().click(ui.getByRole("button", { name: "Inspect task" })); + expect(invoke.mock.calls).toEqual([["/task-watch:inspect"]]); + }); + + it("renders no extension block when there are no surfaces", () => { + const ui = render(); + expect(ui.container.firstChild).toBeNull(); + }); +}); diff --git a/desktop/frontend-next/src/ui/panels/Extensions.tsx b/desktop/frontend-next/src/ui/panels/Extensions.tsx index ecf5cbec47..f2e5a3354d 100644 --- a/desktop/frontend-next/src/ui/panels/Extensions.tsx +++ b/desktop/frontend-next/src/ui/panels/Extensions.tsx @@ -45,7 +45,7 @@ export function Extensions({ {panel.text &&
{panel.text}
} {panel.progress !== undefined && ( - + )}