167 lines
6.0 KiB
TypeScript
167 lines
6.0 KiB
TypeScript
import { expect, test } from "../../fixtures/test-base";
|
|
import { waitForFiniteAnimations } from "../../helpers/animations";
|
|
import { expectControlHeight } from "../../helpers/control-sizing";
|
|
import { enableCanvasFeature } from "./canvas-fixture";
|
|
|
|
const CANVAS_ENTRY = {
|
|
id: "canvas-release-board",
|
|
kind: "canvas",
|
|
name: "Release board",
|
|
description: "A portable release board for workspace planning.",
|
|
author: "Kandev E2E",
|
|
categories: ["planning"],
|
|
icon_url: "",
|
|
repo_url: "https://github.com/example/release-board",
|
|
version: "1.2.0",
|
|
min_kandev_version: "0.94.0",
|
|
license: "MIT",
|
|
package_url: "https://downloads.example.test/canvas-release-board-1.2.0.tar.gz",
|
|
package_sha256: "a".repeat(64),
|
|
previews: [
|
|
{
|
|
url: "https://images.example.test/release-board-cover.png",
|
|
alt: "Release board with grouped milestones",
|
|
},
|
|
{
|
|
url: "https://images.example.test/release-board-detail.png",
|
|
alt: "Release board milestone details",
|
|
},
|
|
],
|
|
permissions: {
|
|
reads: ["tasks"],
|
|
writes: [],
|
|
events: ["task.updated"],
|
|
shared_state: false,
|
|
external_origins: [],
|
|
},
|
|
stars: 12,
|
|
updated_at: "2026-09-01T00:00:00.000Z",
|
|
install_state: "available",
|
|
source_id: "official",
|
|
source_name: "Kandev Official",
|
|
};
|
|
|
|
function canvasCatalog() {
|
|
return {
|
|
plugins: [],
|
|
canvases: [CANVAS_ENTRY],
|
|
sources: [
|
|
{
|
|
id: "official",
|
|
name: "Kandev Official",
|
|
url: "https://registry.example.test/index.json",
|
|
enabled: true,
|
|
builtin: true,
|
|
healthy: true,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
async function mockCanvasCatalog(page: import("@playwright/test").Page) {
|
|
await page.route("https://images.example.test/**", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "image/png",
|
|
body: Buffer.from(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
|
|
"base64",
|
|
),
|
|
});
|
|
});
|
|
await page.route("**/api/plugins/marketplace*", async (route) => {
|
|
if (route.request().method() !== "GET") {
|
|
await route.fallback();
|
|
return;
|
|
}
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(canvasCatalog()),
|
|
});
|
|
});
|
|
}
|
|
|
|
test.describe("Canvas marketplace", () => {
|
|
test("browses a registry canvas, opens details, and keeps manual install available", async ({
|
|
testPage,
|
|
apiClient,
|
|
backend,
|
|
seedData,
|
|
}) => {
|
|
test.setTimeout(90_000);
|
|
const releaseFeature = await enableCanvasFeature(backend, apiClient, seedData.workspaceId);
|
|
try {
|
|
await mockCanvasCatalog(testPage);
|
|
await testPage.goto("/settings/plugins");
|
|
await testPage.getByTestId("plugins-tab-canvases").click();
|
|
|
|
const card = testPage.getByTestId(`canvas-marketplace-entry-${CANVAS_ENTRY.id}`);
|
|
await expect(card).toBeVisible();
|
|
await expect(card.locator(`img[alt="${CANVAS_ENTRY.previews[0].alt}"]`)).toBeVisible();
|
|
await expect(testPage.getByTestId("canvas-marketplace-search")).toBeVisible();
|
|
|
|
const marketplace = testPage.getByTestId("canvas-marketplace");
|
|
const toolbar = marketplace.getByTestId("canvas-marketplace-toolbar");
|
|
await expectControlHeight(toolbar.getByRole("button", { name: "Refresh", exact: true }), 28);
|
|
await expectControlHeight(toolbar.getByRole("button", { name: "Sources", exact: true }), 28);
|
|
await expectControlHeight(
|
|
toolbar.getByRole("button", { name: "Install canvas", exact: true }),
|
|
28,
|
|
);
|
|
await expectControlHeight(marketplace.getByTestId("canvas-marketplace-search"), 28);
|
|
await expectControlHeight(marketplace.getByTestId("canvas-marketplace-workspace"), 28);
|
|
await expectControlHeight(marketplace.getByTestId("canvas-marketplace-category"), 28);
|
|
await expectControlHeight(marketplace.getByTestId("canvas-marketplace-sort"), 28);
|
|
|
|
await card.getByRole("button").first().click();
|
|
const detail = testPage.getByTestId(`canvas-marketplace-detail-${CANVAS_ENTRY.id}`);
|
|
await expect(detail).toBeVisible();
|
|
await expect(detail.getByLabel("Preview images")).toBeVisible();
|
|
await expect(detail.getByText("tasks")).toBeVisible();
|
|
|
|
await detail.getByRole("button", { name: "Review and install", exact: true }).click();
|
|
const installDialog = testPage.getByRole("dialog").last();
|
|
await expect(installDialog).toBeVisible();
|
|
await expect(installDialog.getByText("Release board")).toBeVisible();
|
|
await expect(
|
|
installDialog.getByText("Preview images are registry metadata.", { exact: false }),
|
|
).toBeVisible();
|
|
await expect(installDialog.getByText("Preview images")).toBeVisible();
|
|
|
|
await installDialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
|
await detail.getByRole("button", { name: "Back to canvases", exact: true }).click();
|
|
await testPage
|
|
.getByTestId("canvas-marketplace")
|
|
.getByRole("button", { name: "Install canvas", exact: true })
|
|
.first()
|
|
.click();
|
|
|
|
const manualDialog = testPage.getByRole("dialog").last();
|
|
await expect(
|
|
manualDialog.getByRole("button", { name: "Upload bundle", exact: true }),
|
|
).toBeVisible();
|
|
await waitForFiniteAnimations(manualDialog);
|
|
await expectControlHeight(
|
|
manualDialog.getByRole("button", { name: "Upload bundle", exact: true }),
|
|
28,
|
|
);
|
|
await expectControlHeight(
|
|
manualDialog.getByRole("button", { name: "Direct link", exact: true }),
|
|
28,
|
|
);
|
|
await expectControlHeight(
|
|
manualDialog.getByRole("button", { name: "Cancel", exact: true }),
|
|
28,
|
|
);
|
|
await manualDialog.getByRole("button", { name: "Direct link", exact: true }).click();
|
|
const directLink = manualDialog.getByLabel("Direct link", { exact: true });
|
|
await expect(directLink).toBeVisible();
|
|
await expectControlHeight(directLink, 28);
|
|
await expect(manualDialog.getByText("Screenshot", { exact: false })).toHaveCount(0);
|
|
} finally {
|
|
await releaseFeature();
|
|
}
|
|
});
|
|
});
|