diff --git a/src/features/run/ConfigChooser.tsx b/src/features/run/ConfigChooser.tsx index 2e9b90bf..6be63e45 100644 --- a/src/features/run/ConfigChooser.tsx +++ b/src/features/run/ConfigChooser.tsx @@ -5,9 +5,11 @@ import { shortcutLabel } from "@/platform/keymap"; import { HighlightedText, Icon, IconButton, List, Popup, SearchField, fuzzyMatch, listNavigate, T } from "@/ui"; import type { RunConfig } from "@/platform/ipc"; import { useWorkflowSettings } from "@/platform/workflowSettings"; +import { useDebugRunSessions } from "@/workbench/contributions"; import { commandLine, configIcon, groupConfigs } from "./configs"; import { targetName } from "./remote"; import { canDeleteConfig, CURRENT_FILE, deleteConfig, saveTemporary, selectConfig, startRun, useRun } from "./store"; +import { selectedTarget } from "./sessionTargets"; import styles from "./Run.module.css"; import { tx } from "@/platform/i18n"; @@ -79,6 +81,8 @@ function ConfigChooser() { const selectedId = useRun((s) => s.selectedId); const temporary = useRun((s) => s.temporary); const runTargets = useWorkflowSettings((s) => s.runTargets); + const sessions = useRun((s) => s.sessions); + const debugSessions = useDebugRunSessions((s) => s.sessions); const [query, setQuery] = useState(""); const rows = useMemo( () => @@ -272,13 +276,16 @@ function ConfigChooser() { } if (r.type !== "config") return null; const c = r.config; + const running = selectedTarget(c.id, sessions, debugSessions); return ( - + + + {numbers.size > 0 && {numbers.get(r.key) ?? ""}} {r.indices ? : c.name} @@ -299,7 +306,35 @@ function ConfigChooser() { temporary )} - {isActive && ( + {isActive && running && ( + + { + e.stopPropagation(); + close(); + running.rerun?.(); + }} + /> + { + e.stopPropagation(); + running.stop(); + }} + /> + + )} + {isActive && !running && ( ({ + ...(await importOriginal()), + isTauri: () => false, +})); + +import type { RunConfig } from "@/platform/ipc"; +import { useDebugRunSessions } from "@/workbench/contributions"; +import { ConfigChooserOverlay } from "./ConfigChooser"; +import { resetRuns, useRun, type RunSession } from "./store"; + +const config = (id: string, name: string): RunConfig => ({ + id, + name, + kind: "npm", + command: "run", + args: [], + cwd: null, + env: {}, + detected: true, + isTest: false, + debugAdapter: null, +}); +const server = config("npm:server", "server"); +const worker = config("npm:worker", "worker"); +const idle = config("npm:idle", "idle"); + +const rerun = vi.fn(); +const stop = vi.fn(); +beforeEach(() => { + resetRuns(); + const session = { id: "s1", config: server, runId: null, status: "running", startedAt: 1 } as RunSession; + useRun.setState({ + configs: [server, worker, idle], + loaded: true, + selectedId: server.id, + sessions: [session], + chooserOpen: true, + }); + useDebugRunSessions.setState({ + sessions: [ + { id: 7, configId: worker.id, name: "worker", startedAt: 2, paused: false, rerun, stop, resume: () => {} }, + ], + }); +}); +afterEach(() => { + cleanup(); + useDebugRunSessions.setState({ sessions: [] }); + rerun.mockReset(); + stop.mockReset(); +}); + +const row = (name: string) => screen.getByRole("option", { name: new RegExp(`^${name}`) }); + +it("marks running and debugged configurations", () => { + render(); + const marked = (name: string) => !!row(name).querySelector("[class*='chooserRunning']"); + expect([marked("server"), marked("worker"), marked("idle")]).toEqual([true, true, false]); +}); + +it("offers Rerun and Stop for a running configuration instead of Run and Debug", () => { + render(); + fireEvent.pointerMove(row("worker")); + expect(screen.queryByRole("button", { name: "Run 'worker'" })).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Stop 'worker'" })); + expect(stop).toHaveBeenCalledOnce(); + expect(useRun.getState().chooserOpen).toBe(true); + fireEvent.click(screen.getByRole("button", { name: "Rerun 'worker'" })); + expect(rerun).toHaveBeenCalledOnce(); + expect(useRun.getState().chooserOpen).toBe(false); +}); + +it("keeps Run and Debug for a configuration that isn't running", () => { + render(); + fireEvent.pointerMove(row("idle")); + expect(screen.getByRole("button", { name: "Run 'idle'" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Stop 'idle'" })).not.toBeInTheDocument(); +});