fix(app): confirm terminal close for running shell commands (#52)
This commit is contained in:
@@ -395,6 +395,86 @@ test("terminal tab is removed when shell exits", async ({ page }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("closing terminal with running command asks for confirmation", async ({ page }) => {
|
||||||
|
const repo = await createTempGitRepo("paseo-e2e-terminal-close-confirm-");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await openNewAgentDraft(page);
|
||||||
|
await setWorkingDirectory(page, repo.path);
|
||||||
|
await ensureHostSelected(page);
|
||||||
|
await createAgent(page, "Terminal close confirmation");
|
||||||
|
|
||||||
|
await openTerminalsPanel(page);
|
||||||
|
|
||||||
|
const tabTestId = await getFirstTerminalTabTestId(page);
|
||||||
|
const terminalId = tabTestId.replace("terminal-tab-", "");
|
||||||
|
const tab = page.getByTestId(tabTestId).first();
|
||||||
|
await expect(tab).toBeVisible({ timeout: 30000 });
|
||||||
|
|
||||||
|
const runningMarker = `terminal-close-running-${Date.now()}`;
|
||||||
|
await runTerminalCommand(
|
||||||
|
page,
|
||||||
|
`echo ${runningMarker} && sleep 30`,
|
||||||
|
runningMarker
|
||||||
|
);
|
||||||
|
|
||||||
|
await tab.hover();
|
||||||
|
const dialogPromise = page.waitForEvent("dialog", { timeout: 30000 }).then(
|
||||||
|
async (dialog) => {
|
||||||
|
expect(dialog.type()).toBe("confirm");
|
||||||
|
await dialog.dismiss();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await page.getByTestId(`terminal-close-${terminalId}`).first().click();
|
||||||
|
await dialogPromise;
|
||||||
|
|
||||||
|
await expect(tab).toBeVisible({ timeout: 30000 });
|
||||||
|
} finally {
|
||||||
|
await repo.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("confirming terminal close with running command removes the tab", async ({ page }) => {
|
||||||
|
const repo = await createTempGitRepo("paseo-e2e-terminal-close-confirm-accept-");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await openNewAgentDraft(page);
|
||||||
|
await setWorkingDirectory(page, repo.path);
|
||||||
|
await ensureHostSelected(page);
|
||||||
|
await createAgent(page, "Terminal close confirmation accept");
|
||||||
|
|
||||||
|
await openTerminalsPanel(page);
|
||||||
|
|
||||||
|
const tabTestId = await getFirstTerminalTabTestId(page);
|
||||||
|
const terminalId = tabTestId.replace("terminal-tab-", "");
|
||||||
|
const tab = page.getByTestId(tabTestId).first();
|
||||||
|
await expect(tab).toBeVisible({ timeout: 30000 });
|
||||||
|
|
||||||
|
const runningMarker = `terminal-close-running-accept-${Date.now()}`;
|
||||||
|
await runTerminalCommand(
|
||||||
|
page,
|
||||||
|
`echo ${runningMarker} && sleep 30`,
|
||||||
|
runningMarker
|
||||||
|
);
|
||||||
|
|
||||||
|
await tab.hover();
|
||||||
|
const dialogPromise = page.waitForEvent("dialog", { timeout: 30000 }).then(
|
||||||
|
async (dialog) => {
|
||||||
|
expect(dialog.type()).toBe("confirm");
|
||||||
|
await dialog.accept();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await page.getByTestId(`terminal-close-${terminalId}`).first().click();
|
||||||
|
await dialogPromise;
|
||||||
|
|
||||||
|
await expect(page.getByTestId(tabTestId)).toHaveCount(0, {
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await repo.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test("terminals are shared by agents on the same cwd", async ({ page }) => {
|
test("terminals are shared by agents on the same cwd", async ({ page }) => {
|
||||||
const repo = await createTempGitRepo("paseo-e2e-terminal-share-");
|
const repo = await createTempGitRepo("paseo-e2e-terminal-share-");
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
resolvePendingModifierDataInput,
|
resolvePendingModifierDataInput,
|
||||||
} from "@/utils/terminal-keys";
|
} from "@/utils/terminal-keys";
|
||||||
import { upsertTerminalListEntry } from "@/utils/terminal-list";
|
import { upsertTerminalListEntry } from "@/utils/terminal-list";
|
||||||
|
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||||
import {
|
import {
|
||||||
TerminalOutputPump,
|
TerminalOutputPump,
|
||||||
type TerminalOutputChunk,
|
type TerminalOutputChunk,
|
||||||
@@ -588,13 +589,26 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
}, [activeStreamId, flushPendingTerminalInput]);
|
}, [activeStreamId, flushPendingTerminalInput]);
|
||||||
|
|
||||||
const handleCloseTerminal = useCallback(
|
const handleCloseTerminal = useCallback(
|
||||||
(terminalId: string) => {
|
async (terminalId: string) => {
|
||||||
if (
|
if (
|
||||||
killTerminalMutation.isPending &&
|
killTerminalMutation.isPending &&
|
||||||
killTerminalMutation.variables === terminalId
|
killTerminalMutation.variables === terminalId
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmed = await confirmDialog({
|
||||||
|
title: "Close terminal?",
|
||||||
|
message: "Any running process in this terminal will be stopped immediately.",
|
||||||
|
confirmLabel: "Close",
|
||||||
|
cancelLabel: "Cancel",
|
||||||
|
destructive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
killTerminalMutation.mutate(terminalId);
|
killTerminalMutation.mutate(terminalId);
|
||||||
},
|
},
|
||||||
[killTerminalMutation]
|
[killTerminalMutation]
|
||||||
@@ -882,7 +896,7 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
onHoverOut={() => handleTerminalCloseHoverOut(terminal.id)}
|
onHoverOut={() => handleTerminalCloseHoverOut(terminal.id)}
|
||||||
onPress={(event) => {
|
onPress={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
handleCloseTerminal(terminal.id);
|
void handleCloseTerminal(terminal.id);
|
||||||
}}
|
}}
|
||||||
style={({ hovered, pressed }) => [
|
style={({ hovered, pressed }) => [
|
||||||
styles.terminalTabCloseButton,
|
styles.terminalTabCloseButton,
|
||||||
|
|||||||
Reference in New Issue
Block a user