- détachement: le panneau Paramètres du projet devient détachable (mapping historique panel=context → projectSettings dans parseViewWindowParams, DETACHABLE/ViewPanelBody alignés, ProjectsView ne court-circuitant plus le refus); Retry d'échec de détachement préservé - couleurs projet: projectTheme publie idea:project-theme-changed localement + BroadcastChannel inter-webviews (fenêtres OS détachées), garde de révision par projet dans ProjectsView/ViewWindow contre les lectures settings en course — plus de flash/retour au bleu par défaut - conversation custom CLI: transcript_restore_expected ne considère plus l'absence de scrollback volatile comme une restauration échouée quand la session rouvre en SessionPlan::None (continuation vide valide); CustomAgentChatView n'affiche plus le faux « conversation liée n'a pas pu être restaurée » - images: conservation des images de conversation après changement de projet puis retour - menus Panneaux: retrait des entrées Projects et Project Context (panelOrder WebWorkspace, création/switch via tab-bar +, contexte projet fusionné dans Paramètres du projet) - QA verte: tests frontend ciblés window/ViewWindow/ProjectSettingsPanel/ProjectsView.detach/projects/CustomAgentChatView/WebDesktopWorkspace + cargo test -p application lifecycle transcript_restore_expected; fichiers .ideai/* (tickets 308-315, index/counter, agents/test, chat-drafts, system-permissions, project-settings.json, attachments/) et scripts/ hors périmètre préservés en worktree; aucun push
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const invoke = vi.fn();
|
|
const listen = vi.fn();
|
|
vi.mock("@tauri-apps/api/core", () => ({
|
|
invoke: (...args: unknown[]) => invoke(...args),
|
|
}));
|
|
vi.mock("@tauri-apps/api/event", () => ({
|
|
listen: (...args: unknown[]) => listen(...args),
|
|
}));
|
|
|
|
import { TauriWindowGateway } from "./window";
|
|
|
|
describe("TauriWindowGateway (#23)", () => {
|
|
beforeEach(() => {
|
|
invoke.mockReset().mockResolvedValue({});
|
|
listen.mockReset();
|
|
});
|
|
|
|
it("open/close relay panel-only payloads to the backend commands (#47)", async () => {
|
|
const gw = new TauriWindowGateway();
|
|
await gw.openViewWindow("tickets");
|
|
expect(invoke).toHaveBeenCalledWith("open_view_window", {
|
|
panel: "tickets",
|
|
});
|
|
await gw.closeViewWindow("git");
|
|
expect(invoke).toHaveBeenCalledWith("close_view_window", {
|
|
panel: "git",
|
|
});
|
|
});
|
|
|
|
it("maps project settings to the backend's historical context window id", async () => {
|
|
const gw = new TauriWindowGateway();
|
|
await gw.openViewWindow("projectSettings");
|
|
expect(invoke).toHaveBeenLastCalledWith("open_view_window", {
|
|
panel: "context",
|
|
});
|
|
await gw.closeViewWindow("projectSettings");
|
|
expect(invoke).toHaveBeenLastCalledWith("close_view_window", {
|
|
panel: "context",
|
|
});
|
|
});
|
|
|
|
it("opens plugin layout windows through the backend command", async () => {
|
|
invoke.mockResolvedValueOnce({
|
|
label: "view-plugin-layout",
|
|
url: "index.html?pluginLayout=1",
|
|
alreadyOpen: false,
|
|
providerPluginDisplayName: "Plugin",
|
|
layoutLabel: "Dashboard",
|
|
surface: {
|
|
pluginId: "dev.acme.plugin",
|
|
layoutType: "dev.acme.plugin.dashboard",
|
|
state: { tab: "overview" },
|
|
},
|
|
});
|
|
const gw = new TauriWindowGateway();
|
|
|
|
await expect(
|
|
gw.openPluginLayoutWindow({
|
|
pluginId: "dev.acme.plugin",
|
|
layoutType: "dev.acme.plugin.dashboard",
|
|
state: { tab: "overview" },
|
|
}),
|
|
).resolves.toMatchObject({
|
|
label: "view-plugin-layout",
|
|
surface: { layoutType: "dev.acme.plugin.dashboard" },
|
|
});
|
|
|
|
expect(invoke).toHaveBeenCalledWith("open_plugin_layout_window", {
|
|
input: {
|
|
pluginId: "dev.acme.plugin",
|
|
layoutType: "dev.acme.plugin.dashboard",
|
|
state: { tab: "overview" },
|
|
},
|
|
});
|
|
});
|
|
|
|
it("onViewWindowClosed listens on the lifecycle channel and forwards only closes", async () => {
|
|
// Capture the raw Tauri listener so we can drive lifecycle events at will.
|
|
let raw: ((e: { payload: unknown }) => void) | undefined;
|
|
const unlisten = vi.fn();
|
|
listen.mockImplementation((_name: string, cb: typeof raw) => {
|
|
raw = cb;
|
|
return Promise.resolve(unlisten);
|
|
});
|
|
|
|
const gw = new TauriWindowGateway();
|
|
const handler = vi.fn();
|
|
const off = await gw.onViewWindowClosed(handler);
|
|
|
|
expect(listen).toHaveBeenCalledWith(
|
|
"view-window://lifecycle",
|
|
expect.any(Function),
|
|
);
|
|
|
|
// opened / focused are dropped…
|
|
raw?.({
|
|
payload: { kind: "opened", panel: "git", label: "view-git" },
|
|
});
|
|
raw?.({
|
|
payload: { kind: "focused", panel: "git", label: "view-git" },
|
|
});
|
|
expect(handler).not.toHaveBeenCalled();
|
|
|
|
// …only closed reaches the port handler, narrowed to { panel } (#47).
|
|
raw?.({
|
|
payload: { kind: "closed", panel: "git", label: "view-git" },
|
|
});
|
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
expect(handler).toHaveBeenCalledWith({ panel: "git" });
|
|
|
|
raw?.({
|
|
payload: { kind: "closed", panel: "context", label: "view-context" },
|
|
});
|
|
expect(handler).toHaveBeenLastCalledWith({ panel: "projectSettings" });
|
|
|
|
off();
|
|
expect(unlisten).toHaveBeenCalled();
|
|
});
|
|
});
|