import { afterEach, beforeEach, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { executeAction } from "@/platform/actions"; import { setContext } from "@/platform/context"; import type { RunConfig } from "@/platform/ipc"; import { registerMenuGroup, useDebugRunSessions, type DebugRunSession } from "@/workbench/contributions"; import { useContextMenuStore } from "@/ui"; import { applicationConfig } from "./application"; import { activate } from "./index"; import { RunWidget } from "./RunWidget"; import { resetRuns, useRun, type RunSession } from "./store"; import { selectedTarget } from "./sessionTargets"; const config = (id: string): RunConfig => ({ id, name: id, kind: "shell", command: "sleep", args: ["60"], cwd: null, env: {}, detected: false, isTest: false, debugAdapter: null, }); const app = config("SdCloudOaApplication"); const npm = config("sdcloud-oa-vue3: npm run dev"); const run = (c: RunConfig, startedAt = 1): RunSession => ({ id: c.id, config: c, runId: 1, status: "running", exitCode: null, startedAt, endedAt: null, stdinClosed: false, }); const debug = (c: RunConfig, startedAt = 2): DebugRunSession => ({ id: 1, configId: c.id, name: c.name, startedAt, paused: false, rerun: vi.fn(), stop: vi.fn(), resume: vi.fn(), }); let dispose: () => void; beforeEach(() => { resetRuns(); useRun.setState({ configs: [app, npm], selectedId: app.id, loaded: true }); useDebugRunSessions.setState({ sessions: [] }); setContext("projectOpen", true); dispose = activate(); }); afterEach(() => { cleanup(); dispose(); useDebugRunSessions.setState({ sessions: [] }); }); it("reruns and stops the selected configuration among several running sessions", () => { useRun.setState({ sessions: [run(app), run(npm, 3)] }); render(); expect(screen.getByRole("button", { name: `Rerun '${app.name}'` })).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: `Stop '${app.name}'` })); expect(useRun.getState().sessions.map((s) => !!s.stopped)).toEqual([true, false]); }); it("offers Run/Debug for an idle selection and Stop for the other process", () => { useRun.setState({ sessions: [run(npm)] }); render(); expect(screen.getByRole("button", { name: `Run '${app.name}'` })).toBeTruthy(); expect(screen.getByRole("button", { name: `Debug '${app.name}'` })).toBeTruthy(); expect(screen.queryByRole("button", { name: /Rerun/ })).toBeNull(); fireEvent.click(screen.getByRole("button", { name: `Stop '${npm.name}'` })); expect(useRun.getState().sessions[0]?.stopped).toBe(true); }); it("offers a stop list when the selection is idle and several other sessions run", () => { useRun.setState({ sessions: [run(npm), run(config("other"))] }); render(); expect(screen.getByRole("button", { name: "Stop (2 running)" })).toBeTruthy(); expect(screen.queryByRole("button", { name: /Rerun/ })).toBeNull(); }); it("targets the selected debug session while npm runs, through buttons and shortcut actions", async () => { const session = debug(app); useRun.setState({ sessions: [run(npm, 3)] }); useDebugRunSessions.setState({ sessions: [session] }); render(); fireEvent.click(screen.getByRole("button", { name: `Rerun '${app.name}'` })); await vi.waitFor(() => expect(session.rerun).toHaveBeenCalledOnce()); await executeAction("run.run"); expect(session.rerun).toHaveBeenCalledTimes(2); fireEvent.click(screen.getByRole("button", { name: `Stop '${app.name}'` })); await vi.waitFor(() => expect(session.stop).toHaveBeenCalledOnce()); expect(useRun.getState().sessions[0]?.stopped).toBeUndefined(); }); it("lists every running process with Stop All for the Stop shortcut, like IDEA", async () => { const session = debug(app); useRun.setState({ sessions: [run(npm, 3)] }); useDebugRunSessions.setState({ sessions: [session] }); const unregister = registerMenuGroup({ id: "test.stopDebug", menus: ["stopProcess"], items: () => [{ label: app.name, run: () => session.stop() }], }); try { render(); await executeAction("run.stop"); const items = useContextMenuStore.getState().menu!.items; expect(items.map((i) => ("label" in i ? i.label : i.kind))).toEqual([ "Stop Process", npm.name, app.name, "separator", "Stop All", ]); const stopAll = items.at(-1) as { run: () => void }; stopAll.run(); expect(session.stop).toHaveBeenCalledOnce(); expect(useRun.getState().sessions[0]?.stopped).toBe(true); } finally { unregister(); } }); it("ignores an unrelated debug session for an idle selection", () => { useDebugRunSessions.setState({ sessions: [debug(npm)] }); render(); expect(screen.getByRole("button", { name: `Run '${app.name}'` })).toBeTruthy(); expect(screen.queryByRole("button", { name: /Rerun/ })).toBeNull(); }); it.each([1, 3])("prefers the newest Run or Debug session of the same config (run start %s)", (startedAt) => { expect(selectedTarget(app.id, [run(app, startedAt)], [debug(app, 2)])?.debug).toBe(startedAt < 2); }); it("matches a detected Spring configuration after its object and id are regenerated", () => { const detect = () => applicationConfig( { className: "com.example.SdCloudOaApplication", spring: true }, "src/main/java/com/example/SdCloudOaApplication.java", ); const original = detect(); const regenerated = detect(); expect(regenerated).not.toBe(original); expect(regenerated.id).toBe(original.id); expect(selectedTarget(regenerated.id, [run(original), run(npm)], [])?.name).toBe(original.name); });