49f87c4da3
Whole tree from parallel sessions, plus the version bump: - Clone: a queue with live progress (Add workspace and extra folders), clones run under nice/ionice so the machine stays responsive. - Settings → AI tools → Session defaults: a new session's default permission mode / bypass per account. - Chat lists the background subagents that have not reported back yet. - A file pasted or dropped into a claude pane goes in as an @ mention. - Changes panel: say why amend/undo is refused on a pushed commit. - openapi.yaml regenerated (Artifacts, Host, resumeById).
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
/**
|
|
* @file SessionDefaultsSettings.test.tsx
|
|
* @description Session defaults: Bypass becomes Claude's default permission
|
|
* mode or Codex's default bypass only after the confirm, a cancelled confirm
|
|
* keeps the old default, the plain modes save at once, and the pet chat's
|
|
* safe read never returns bypass.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { SessionDefaultsSettings } from "../SessionDefaultsSettings";
|
|
import { confirmDialog } from "../../../lib/confirm";
|
|
import { readCodexDefaults, readDefaultMode } from "../../../lib/sessionDefaults";
|
|
|
|
const aiTools = vi.hoisted(() => ({
|
|
tools: [
|
|
{ id: "claude", label: "Claude Code", installed: true, enabled: true },
|
|
{ id: "codex", label: "Codex", installed: true, enabled: true },
|
|
],
|
|
}));
|
|
vi.mock("../../../hooks/useAiTools", () => ({ useAiTools: () => aiTools }));
|
|
vi.mock("../../../lib/confirm", () => ({ confirmDialog: vi.fn() }));
|
|
const confirm = vi.mocked(confirmDialog);
|
|
|
|
const radio = (name: RegExp) => screen.getAllByRole("radio", { name })[0]!;
|
|
|
|
describe("SessionDefaultsSettings", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
confirm.mockReset();
|
|
});
|
|
|
|
it("saves a plain mode without asking", () => {
|
|
render(<SessionDefaultsSettings />);
|
|
fireEvent.click(radio(/plan/i));
|
|
expect(confirm).not.toHaveBeenCalled();
|
|
expect(readDefaultMode()).toBe("plan");
|
|
});
|
|
|
|
it("makes Bypass Claude's default only once confirmed", async () => {
|
|
confirm.mockResolvedValueOnce(false);
|
|
render(<SessionDefaultsSettings />);
|
|
fireEvent.click(radio(/bypass/i));
|
|
await waitFor(() => expect(confirm).toHaveBeenCalledTimes(1));
|
|
expect(readDefaultMode()).toBe("auto");
|
|
|
|
confirm.mockResolvedValueOnce(true);
|
|
fireEvent.click(radio(/bypass/i));
|
|
await waitFor(() => expect(readDefaultMode()).toBe("bypassPermissions"));
|
|
expect(readDefaultMode({ safe: true })).toBe("auto");
|
|
});
|
|
|
|
it("turns Codex's default bypass on only once confirmed, off without asking", async () => {
|
|
confirm.mockResolvedValueOnce(true);
|
|
render(<SessionDefaultsSettings />);
|
|
const sw = screen.getByRole("switch");
|
|
fireEvent.click(sw);
|
|
await waitFor(() => expect(readCodexDefaults().bypass).toBe(true));
|
|
fireEvent.click(screen.getByRole("switch"));
|
|
await waitFor(() => expect(readCodexDefaults().bypass).toBe(false));
|
|
expect(confirm).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("shows nothing when neither Claude nor Codex is enabled", () => {
|
|
const saved = aiTools.tools;
|
|
aiTools.tools = [];
|
|
const { container } = render(<SessionDefaultsSettings />);
|
|
expect(container.firstChild).toBeNull();
|
|
aiTools.tools = saved;
|
|
});
|
|
});
|