fix(app-tauri,frontend): la fenêtre Tâches planifiées s'affiche en mode détaché — #359 (QA verte)

cause: le panneau Tâches planifiées n'avait aucune variante dans l'enum ViewPanel, open_view_window("scheduledTasks") rejetait INVALID et detachPanel ne touchait le placement qu'en cas de succès — même famille que #299, aucun effet visible ni message.
fix: variante ViewPanel::ScheduledTasks avec contrat stable label view-scheduledTasks / url index.html?panel=scheduledTasks / titre « Scheduled tasks », identité persistée des fenêtres view-scheduledTasks acceptée à la restauration #50; frontend ViewWindow rend le ScheduledTasksPanel du projet focalisé et ProjectsView expose le placement Fenêtre détachée via WindowGateway; retrait de la variante Projects devenue morte après #315 — le panneau Projects (coquille de la fenêtre principale) n'est plus détachable, refus INVALID gardé par tests.
QA verte: view_window_tests dont scheduled_tasks_view_window_contract_is_stable et projects_panel_is_not_detachable, persisted_identity_accepts_scheduled_tasks_view_labels, frontend ViewWindow/window gateway/ProjectsView.detach verts.
This commit is contained in:
2026-09-15 09:20:28 +02:00
parent 7de1d0afbe
commit 31ca03c29b
5 changed files with 103 additions and 4 deletions

View File

@ -41,6 +41,14 @@ describe("TauriWindowGateway (#23)", () => {
});
});
it("passes scheduledTasks unchanged to the detached-window command", async () => {
await new TauriWindowGateway().openViewWindow("scheduledTasks");
expect(invoke).toHaveBeenCalledWith("open_view_window", {
panel: "scheduledTasks",
});
});
it("opens plugin layout windows through the backend command", async () => {
invoke.mockResolvedValueOnce({
label: "view-plugin-layout",

View File

@ -10,6 +10,7 @@ import { render, screen, waitFor, act, fireEvent } from "@testing-library/react"
import { DIProvider } from "@/app/di";
import {
createMockGateways,
MockAgentGateway,
MockFocusedProjectGateway,
MockProjectGateway,
@ -66,6 +67,13 @@ describe("parseViewWindowParams (#47)", () => {
});
});
it("accepts the detachable scheduled tasks panel", () => {
expect(parseViewWindowParams("?panel=scheduledTasks")).toEqual({
kind: "panel",
panel: "scheduledTasks",
});
});
it("parses a plugin layout window query from the backend base64url contract", () => {
expect(
parseViewWindowParams(
@ -170,6 +178,29 @@ describe("PluginLayoutWindow (#143)", () => {
});
describe("ViewWindow (#47)", () => {
it("renders ScheduledTasksPanel for a focused project", async () => {
const gateways = createMockGateways();
const project = await gateways.project.createProject("alpha", "/p/a");
const focusedProject = new MockFocusedProjectGateway();
gateways.focusedProject = focusedProject;
await focusedProject.setFocusedProject({
id: project.id,
name: project.name,
root: project.root,
});
render(
<DIProvider gateways={gateways}>
<ViewWindow panel="scheduledTasks" />
</DIProvider>,
);
await waitFor(() => expect(screen.getByText("· alpha")).toBeTruthy());
expect(screen.getByRole("heading", { name: "Tâches planifiées", level: 1 })).toBeTruthy();
expect(await screen.findByRole("button", { name: "Nouvelle tâche planifiée" })).toBeTruthy();
expect(await screen.findByText("Aucune tâche planifiée pour ce projet")).toBeTruthy();
});
it("keeps the latest focused project when the initial snapshot is delayed", async () => {
const system = new MockSystemGateway();
const project = new MockProjectGateway();

View File

@ -92,6 +92,17 @@ describe("ProjectsView detach to OS window (#23)", () => {
expect(screen.queryByRole("dialog", { name: "MCP" })).toBeNull();
});
it("detaches scheduled tasks through WindowGateway", async () => {
const { windowGateway } = await renderWithProject();
const openViewWindow = vi.spyOn(windowGateway, "openViewWindow");
pickPlacement("Tâches planifiées", "Fenêtre détachée");
await waitFor(() => expect(openViewWindow).toHaveBeenCalledWith("scheduledTasks"));
expect(windowGateway.open.has("scheduledTasks")).toBe(true);
expect(screen.queryByRole("dialog", { name: "Tâches planifiées" })).toBeNull();
});
it("detaches project settings through the same retry-safe window path", async () => {
const { windowGateway } = await renderWithProject();