diff --git a/ui/src/components/IssueScheduledRetryCard.test.tsx b/ui/src/components/IssueScheduledRetryCard.test.tsx index cd1078b2..d3f6e22c 100644 --- a/ui/src/components/IssueScheduledRetryCard.test.tsx +++ b/ui/src/components/IssueScheduledRetryCard.test.tsx @@ -63,13 +63,13 @@ function buildRetryResponse(outcome: IssueRetryNowOutcome) { }; } -async function flushAll() { - for (let i = 0; i < 4; i += 1) { - // eslint-disable-next-line no-await-in-loop +async function waitForUi(assertion: () => void) { + await vi.waitFor(async () => { await act(async () => { await Promise.resolve(); }); - } + assertion(); + }); } function renderWithProviders(ui: ReactNode) { @@ -174,11 +174,12 @@ describe("IssueScheduledRetryCard", () => { act(() => { button!.click(); }); - await flushAll(); - expect(retryNowMock).toHaveBeenCalledWith("issue-1"); - const finalButton = getRetryNowButton(); - expect(finalButton!.textContent ?? "").toContain("Promoted"); - expect(finalButton!.disabled).toBe(true); + await waitForUi(() => { + expect(retryNowMock).toHaveBeenCalledWith("issue-1"); + const finalButton = getRetryNowButton(); + expect(finalButton!.textContent ?? "").toContain("Promoted"); + expect(finalButton!.disabled).toBe(true); + }); }); it("shows already promoted state when backend reports duplicate click", async () => { @@ -189,9 +190,10 @@ describe("IssueScheduledRetryCard", () => { act(() => { getRetryNowButton()!.click(); }); - await flushAll(); - expect(getRetryNowButton()!.textContent ?? "").toContain("Already promoted"); - expect(container.querySelector('[data-testid="issue-scheduled-retry-error-band"]')).toBeNull(); + await waitForUi(() => { + expect(getRetryNowButton()!.textContent ?? "").toContain("Already promoted"); + expect(container.querySelector('[data-testid="issue-scheduled-retry-error-band"]')).toBeNull(); + }); }); it("renders an inline error band on backend failure", async () => { @@ -202,11 +204,12 @@ describe("IssueScheduledRetryCard", () => { act(() => { getRetryNowButton()!.click(); }); - await flushAll(); - const band = container.querySelector('[data-testid="issue-scheduled-retry-error-band"]'); - expect(band).not.toBeNull(); - expect((band?.textContent ?? "")).toContain("Server error"); - expect(getRetryNowButton()!.disabled).toBe(false); + await waitForUi(() => { + const band = container.querySelector('[data-testid="issue-scheduled-retry-error-band"]'); + expect(band).not.toBeNull(); + expect((band?.textContent ?? "")).toContain("Server error"); + expect(getRetryNowButton()!.disabled).toBe(false); + }); }); it("surfaces gate-suppressed outcome via the inline error band", async () => { @@ -217,10 +220,11 @@ describe("IssueScheduledRetryCard", () => { act(() => { getRetryNowButton()!.click(); }); - await flushAll(); - const band = container.querySelector('[data-testid="issue-scheduled-retry-error-band"]'); - expect(band).not.toBeNull(); - expect((band?.textContent ?? "")).toContain("Promotion suppressed"); - expect(getRetryNowButton()!.disabled).toBe(false); + await waitForUi(() => { + const band = container.querySelector('[data-testid="issue-scheduled-retry-error-band"]'); + expect(band).not.toBeNull(); + expect((band?.textContent ?? "")).toContain("Promotion suppressed"); + expect(getRetryNowButton()!.disabled).toBe(false); + }); }); }); diff --git a/ui/src/components/Sidebar.test.tsx b/ui/src/components/Sidebar.test.tsx index 6bfcd9ae..d4192064 100644 --- a/ui/src/components/Sidebar.test.tsx +++ b/ui/src/components/Sidebar.test.tsx @@ -95,6 +95,24 @@ async function flushReact() { describe("Sidebar", () => { let container: HTMLDivElement; + async function renderSidebar() { + const root = createRoot(container); + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + + await act(async () => { + root.render( + + + , + ); + }); + await flushReact(); + + return root; + } + beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); @@ -107,21 +125,23 @@ describe("Sidebar", () => { vi.clearAllMocks(); }); - it("does not flash the Workspaces link while experimental settings are loading", async () => { - mockInstanceSettingsApi.getExperimental.mockImplementation(() => new Promise(() => {})); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); + it("links the top search icon to the search page without showing Search in Work nav", async () => { + mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: false }); + const root = await renderSidebar(); + + const topSearchLink = container.querySelector('a[aria-label="Search"]'); + expect(topSearchLink?.getAttribute("href")).toBe("/search"); + const workLinks = [...container.querySelectorAll("nav a")].map((anchor) => anchor.textContent?.trim()); + expect(workLinks).not.toContain("Search"); await act(async () => { - root.render( - - - , - ); + root.unmount(); }); - await flushReact(); + }); + + it("does not flash the Workspaces link while experimental settings are loading", async () => { + mockInstanceSettingsApi.getExperimental.mockImplementation(() => new Promise(() => {})); + const root = await renderSidebar(); expect(container.textContent).not.toContain("Workspaces"); @@ -132,19 +152,7 @@ describe("Sidebar", () => { it("shows the Workspaces link when isolated workspaces are enabled", async () => { mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: true }); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - await act(async () => { - root.render( - - - , - ); - }); - await flushReact(); + const root = await renderSidebar(); const link = [...container.querySelectorAll("a")].find((anchor) => anchor.textContent === "Workspaces"); expect(link?.getAttribute("href")).toBe("/workspaces"); diff --git a/ui/src/components/Sidebar.tsx b/ui/src/components/Sidebar.tsx index f202b6d9..a0bc7195 100644 --- a/ui/src/components/Sidebar.tsx +++ b/ui/src/components/Sidebar.tsx @@ -14,6 +14,7 @@ import { Settings, } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; +import { NavLink } from "@/lib/router"; import { SidebarSection } from "./SidebarSection"; import { SidebarNavItem } from "./SidebarNavItem"; import { SidebarProjects } from "./SidebarProjects"; @@ -45,10 +46,6 @@ export function Sidebar() { const liveRunCount = liveRuns?.length ?? 0; const showWorkspacesLink = experimentalSettings?.enableIsolatedWorkspaces === true; - function openSearch() { - document.dispatchEvent(new KeyboardEvent("keydown", { key: "k", metaKey: true })); - } - const pluginContext = { companyId: selectedCompanyId, companyPrefix: selectedCompany?.issuePrefix ?? null, @@ -60,12 +57,16 @@ export function Sidebar() {
@@ -99,7 +100,6 @@ export function Sidebar() { - {showWorkspacesLink ? (