docs(tickets): consigne le cycle #120 fix CORS + décision Git et états (#120)

- carnet.md: enregistre la livraison CORS et la décision Git du 2026-08-01
- issue.md + index.json: mise à jour version/timestamp
- plugins.test.tsx: tests d'intégration frontend liés
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 19:12:39 +02:00
parent a88307c5ff
commit efaeb31a38
4 changed files with 129 additions and 12 deletions

View File

@ -7,8 +7,8 @@ import { describe, it, expect } from "vitest";
import { render, screen, waitFor, fireEvent, within } from "@testing-library/react";
import { MockPluginGateway, MockSystemGateway } from "@/adapters/mock";
import type { PluginInstallResult, PluginRuntimeContributionCatalog } from "@/domain";
import type { Gateways } from "@/ports";
import type { PluginInstallResult, PluginReview, PluginRuntimeContributionCatalog } from "@/domain";
import type { Gateways, ReviewPluginPackageInput } from "@/ports";
import { DIProvider } from "@/app/di";
import { PluginRuntimeRegistry } from "@/plugins/runtime";
import { PluginsPanel } from "./PluginsPanel";
@ -77,6 +77,25 @@ class FailingRuntimeCatalogPluginGateway extends MockPluginGateway {
}
}
class BackendShapedReviewPluginGateway extends MockPluginGateway {
async reviewPackage(input: ReviewPluginPackageInput): Promise<PluginReview> {
const label = input.path.split("/").pop() ?? input.path;
return {
id: "dev.acme.gitgraph",
displayName: label.replace(/\.(ideaplug|zip|vsix)$/i, ""),
publisher: "Acme",
version: "1.2.3",
description: "Graph",
sourceKind: input.sourceKind,
sourceLabel: input.path,
contentHash: "abc",
trustLevel: "full",
contributionSummary: { topLevelMenus: 1, menuItems: 2, layouts: 3, mcpServers: 4 },
contributes: { menus: [], menuItems: [], layouts: [], mcpServers: [] },
} as unknown as PluginReview;
}
}
describe("PluginsPanel", () => {
it("shows an empty state with no plugins installed", async () => {
renderPanel();
@ -122,6 +141,30 @@ describe("PluginsPanel", () => {
expect(screen.getByText("Activé")).toBeTruthy();
});
it("keeps install, uninstall and reinstall stable with backend-shaped reviews", async () => {
renderPanel(new BackendShapedReviewPluginGateway());
await screen.findByText("Aucun plugin installé.");
fireEvent.click(screen.getByRole("button", { name: "Installer depuis une archive…" }));
let dialog = await screen.findByRole("dialog");
expect(within(dialog).getByText(/full-trust/)).toBeTruthy();
fireEvent.click(within(dialog).getByRole("button", { name: "Installer" }));
expect(await screen.findByText("mock-plugin")).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: "Désinstaller" }));
dialog = await screen.findByRole("dialog");
fireEvent.click(within(dialog).getByRole("button", { name: "Désinstaller" }));
expect(await screen.findByText("Aucun plugin installé.")).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: "Installer depuis une archive…" }));
dialog = await screen.findByRole("dialog");
expect(within(dialog).getByRole("button", { name: "Installer" })).toBeTruthy();
fireEvent.click(within(dialog).getByRole("button", { name: "Installer" }));
expect(await screen.findByText("mock-plugin")).toBeTruthy();
expect(screen.queryByText("INTERFACE INTERROMPUE")).toBeNull();
});
it("renders an invalid plugin returned after install as isolated, without replacing the panel", async () => {
renderPanel(new InvalidInstallPluginGateway());
await screen.findByText("Aucun plugin installé.");