feat: publish and synchronize workspace drafts

This commit is contained in:
2026-08-04 06:47:10 +02:00
parent 6b40fa0cc5
commit b75b3af28e
9 changed files with 545 additions and 16 deletions
+53 -1
View File
@@ -2,7 +2,7 @@ import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http, HttpResponse } from "msw";
import { beforeEach, expect, test } from "vitest";
import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { server } from "../test/msw";
import { WorkspaceManager } from "./WorkspaceManager";
@@ -38,6 +38,8 @@ beforeEach(() => {
);
});
afterEach(() => vi.unstubAllGlobals());
test("lists registry workspaces and saves a new workspace only as a browser draft", async () => {
const user = userEvent.setup();
renderManager();
@@ -53,6 +55,56 @@ test("lists registry workspaces and saves a new workspace only as a browser draf
expect(localStorage.getItem("thothii.workspace-registry.v1.draft.trial-registry")).not.toBeNull();
});
test("imports a bundle as a local draft and never publishes it automatically", async () => {
const user = userEvent.setup();
const publishSpy = vi.fn();
server.use(
http.post("http://localhost:8787/workspaces/import", () => HttpResponse.json({ draft: { workspace, contract: {} } })),
http.post("http://localhost:8787/workspaces/publish", () => {
publishSpy();
return HttpResponse.json({});
}),
);
renderManager();
await user.upload(screen.getByLabelText("Import workspace bundle"), new File(["bundle"], "workspace.zip", { type: "application/zip" }));
expect(await screen.findByText("Imported draft saved in this browser. Validate it before publishing.")).toBeVisible();
expect(publishSpy).not.toHaveBeenCalled();
expect(localStorage.getItem("thothii.workspace-registry.v1.draft.psd-clinical")).not.toBeNull();
});
test("pulls and exports only when the manager explicitly requests each action", async () => {
const user = userEvent.setup();
const publishSpy = vi.fn();
const createObjectURL = vi.fn(() => "blob:workspace-bundle");
const revokeObjectURL = vi.fn();
class DownloadUrl extends URL {
static createObjectURL = createObjectURL;
static revokeObjectURL = revokeObjectURL;
}
vi.stubGlobal("URL", DownloadUrl);
vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => undefined);
server.use(
http.post("http://localhost:8787/workspace-registry/pull", () => HttpResponse.json({ branch: "main", head: "c".repeat(40), ahead: 0, behind: 0, degraded: false })),
http.get("http://localhost:8787/workspaces/psd-clinical/export", () => new HttpResponse(new Blob(["bundle"], { type: "application/zip" }))),
http.post("http://localhost:8787/workspaces/publish", () => {
publishSpy();
return HttpResponse.json({});
}),
);
renderManager();
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
await user.click(screen.getByRole("button", { name: "Pull latest registry" }));
expect(await screen.findByText("Registry updated. Reload a workspace to review its latest revision.")).toBeVisible();
await user.click(screen.getByRole("button", { name: "Export workspace bundle" }));
await waitFor(() => expect(createObjectURL).toHaveBeenCalledTimes(1));
expect(revokeObjectURL).toHaveBeenCalledWith("blob:workspace-bundle");
expect(publishSpy).not.toHaveBeenCalled();
});
test("stages duplicate and delete operations without publishing", async () => {
const user = userEvent.setup();
let published = false;