Files
DeepSeek-Reasonix/desktop/frontend-next/src/ui/settings-preset-leave.interaction.test.tsx
T
SivanCola 17327b4875 feat(studio): add preset cards and explicit new-task defaults
Problem: Built-in presets use flat rows, choosing a future task mode requires
repetition, and persona fields are hidden behind an advanced disclosure.

Root cause: Management navigation and new-task launch intent have no shared
explicit preference; the editor groups basic persona fields with identifiers.

Fix: Match the selected two-column card hierarchy with badges, IDs, descriptions
and separate details, usage and copy actions. Persist an explicit built-in card
choice in a new machine-local key and inject it through the local/remote open
owners only for new tasks. Explicit request choices override it; restoring
history keeps its binding. Existing server preferences alone remain inert.
Expose both persona textareas, runtime context, scope and ID directly, rename
settings navigation to Persona, and keep literal persona fields authoritative.

Verification: Full frontend suite plus real MockHub launch effects, blank-pane
reuse, local/remote history, explicit overrides, storage compatibility/refusal,
cross-window refresh and independent footer actions. Production build/typecheck,
UI census/fixtures, format, vet, lint, changed-file and builtin/boot checks.
Browser-rendered visual comparison remains blocked by the URL policy, as
recorded in design-qa.md.
2026-10-20 00:15:23 +08:00

76 lines
3.6 KiB
TypeScript

// @vitest-environment jsdom
import "./testkit";
import { act, cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, expect, it, vi } from "vitest";
import { Settings } from "./Settings";
import { MockPort } from "../port/mock";
import { MockHub } from "../port/mock_hub";
afterEach(cleanup);
function draw(port = new MockPort()) {
const onClose = vi.fn();
render(<Settings hub={new MockHub()} port={port} status={null} theme="light"
onTheme={() => {}} contrast="" onContrast={() => {}} weight="" onWeight={() => {}}
look={{}} onLook={() => {}} reloadThemes={() => {}} onClose={onClose}
onChanged={() => {}} onError={() => {}} at="persona" account={null}
accountUnread="" reloadAccount={() => {}} />);
return { onClose, port };
}
it.each(["close", "navigation", "search"])("protects a draft on settings %s", async (destination) => {
const user = userEvent.setup();
const { onClose } = draw();
expect(screen.getByRole("tab", { name: "人设" })).toBeTruthy();
await user.click(await screen.findByRole("button", { name: "新建预设" }));
await user.type(screen.getByLabelText("预设名称"), "保留草稿");
const leave = async () => {
if (destination === "close") await user.click(screen.getByRole("button", { name: /关闭.*Esc/ }));
else if (destination === "navigation") await user.click(screen.getAllByRole<HTMLButtonElement>("tab").find(el => el.id === "prefs-session")!);
else {
const search = screen.getByRole("textbox", { name: "搜索设置" });
await user.clear(search);
await user.type(search, "模型偏好");
await user.click(screen.getAllByRole<HTMLButtonElement>("option").find(el => el.dataset.target === "model")!);
}
};
await leave();
await screen.findByRole("alertdialog");
await user.keyboard("{Escape}");
expect(screen.queryByRole("alertdialog")).toBeNull();
expect((screen.getByLabelText("预设名称") as HTMLInputElement).value).toBe("保留草稿");
expect(onClose).not.toHaveBeenCalled();
await leave();
await user.click(screen.getByRole("button", { name: "放弃更改" }));
if (destination === "close") expect(onClose).toHaveBeenCalledOnce();
else expect(screen.queryByRole("form", { name: "新建预设" })).toBeNull();
});
it("uses Escape to return to the catalog without closing Settings", async () => {
const user = userEvent.setup();
const { onClose } = draw();
await user.click(await screen.findByRole("button", { name: "新建预设" }));
await user.keyboard("{Escape}");
expect(screen.getByRole("button", { name: "新建预设" })).toBeTruthy();
expect(onClose).not.toHaveBeenCalled();
});
it("keeps a pending save mounted when Settings is closed", async () => {
const user = userEvent.setup();
const port = new MockPort();
const save = port.saveAgentPreset.bind(port);
let complete!: () => void;
vi.spyOn(port, "saveAgentPreset").mockImplementation((draft, revision) =>
new Promise(resolve => { complete = () => { void save(draft, revision).then(resolve); }; }));
const { onClose } = draw(port);
await user.click(await screen.findByRole("button", { name: "新建预设" }));
await user.type(screen.getByLabelText("预设名称"), "保存中");
await user.click(screen.getByRole("button", { name: "保存预设" }));
await user.click(screen.getByRole("button", { name: /关闭.*Esc/ }));
expect(onClose).not.toHaveBeenCalled();
expect(screen.getByRole<HTMLButtonElement>("button", { name: "取消" }).disabled).toBe(true);
await act(async () => complete());
expect(await screen.findByRole("button", { name: "编辑 保存中" })).toBeTruthy();
});