Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
306 lines
9.4 KiB
TypeScript
306 lines
9.4 KiB
TypeScript
/**
|
|
* Widgets E2E Tests
|
|
*
|
|
* Tests widget area management at /widgets.
|
|
* Covers creating widget areas, adding widgets, and deleting areas.
|
|
*
|
|
* The fixture starts with no widget areas, so tests create their own.
|
|
*/
|
|
|
|
import { test, expect } from "../fixtures";
|
|
|
|
// API helper
|
|
function apiHeaders(token: string, baseUrl: string) {
|
|
return {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
"X-EmDash-Request": "1",
|
|
Origin: baseUrl,
|
|
};
|
|
}
|
|
|
|
test.describe("Widgets", () => {
|
|
let headers: Record<string, string>;
|
|
let baseUrl: string;
|
|
|
|
test.beforeEach(async ({ admin, serverInfo }) => {
|
|
await admin.devBypassAuth();
|
|
baseUrl = serverInfo.baseUrl;
|
|
headers = apiHeaders(serverInfo.token, baseUrl);
|
|
});
|
|
|
|
test.describe("Widget Areas Page", () => {
|
|
test("renders the widgets page", async ({ admin, page }) => {
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Should show the page title
|
|
await expect(page.locator("h1").first()).toContainText("Widgets");
|
|
|
|
// Should show the "Add Widget Area" button
|
|
await expect(page.getByRole("button", { name: "Add Widget Area" })).toBeVisible();
|
|
|
|
// Should show the available widgets palette
|
|
await expect(page.locator("h2", { hasText: "Available Widgets" })).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
});
|
|
|
|
test("shows empty state when no widget areas exist", async ({ admin, page }) => {
|
|
// Clean up any existing areas first
|
|
const res = await fetch(`${baseUrl}/_emdash/api/widget-areas`, { headers });
|
|
if (res.ok) {
|
|
const data: any = await res.json();
|
|
const areas = data.data?.areas ?? [];
|
|
for (const area of areas) {
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas/${area.name}`, {
|
|
method: "DELETE",
|
|
headers,
|
|
}).catch(() => {});
|
|
}
|
|
}
|
|
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Should show empty state
|
|
await expect(page.locator("text=No widget areas yet")).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test("shows built-in widget types in the palette", async ({ admin, page }) => {
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Should show Content Block and Menu in the palette
|
|
await expect(page.locator("text=Content Block").first()).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator("text=Menu").first()).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Create Widget Area", () => {
|
|
test("opens and closes the create widget area dialog", async ({ admin, page }) => {
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Click "Add Widget Area"
|
|
await page.getByRole("button", { name: "Add Widget Area" }).click();
|
|
|
|
// Dialog should appear
|
|
const dialog = page.getByRole("dialog", { name: "Create Widget Area" });
|
|
await expect(dialog).toBeVisible({ timeout: 5000 });
|
|
|
|
// Should have Name, Label, Description fields
|
|
await expect(dialog.getByLabel("Name")).toBeVisible();
|
|
await expect(dialog.getByLabel("Label")).toBeVisible();
|
|
|
|
// Cancel should close the dialog
|
|
await dialog.getByRole("button", { name: "Cancel" }).click();
|
|
await expect(dialog).not.toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test("creates a new widget area", async ({ admin, page }) => {
|
|
const areaName = `e2e-area-${Date.now()}`;
|
|
const areaLabel = "E2E Test Area";
|
|
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Open create dialog
|
|
await page.getByRole("button", { name: "Add Widget Area" }).click();
|
|
|
|
const dialog = page.getByRole("dialog", { name: "Create Widget Area" });
|
|
await expect(dialog).toBeVisible({ timeout: 5000 });
|
|
|
|
// Fill form
|
|
await dialog.getByLabel("Name").fill(areaName);
|
|
await dialog.getByLabel("Label").fill(areaLabel);
|
|
await dialog.getByLabel("Description").fill("Area for E2E testing");
|
|
|
|
// Submit
|
|
await dialog.getByRole("button", { name: "Create" }).click();
|
|
|
|
// Dialog should close
|
|
await expect(dialog).not.toBeVisible({ timeout: 10000 });
|
|
|
|
// New area should appear in the list
|
|
await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 });
|
|
|
|
// Clean up via API
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, {
|
|
method: "DELETE",
|
|
headers,
|
|
}).catch(() => {});
|
|
});
|
|
});
|
|
|
|
test.describe("Add Widget to Area", () => {
|
|
test("adds a content widget to an area via API and verifies in UI", async ({ admin, page }) => {
|
|
const areaName = `e2e-widget-${Date.now()}`;
|
|
const areaLabel = "Widget Test Area";
|
|
|
|
// Create area via API
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ name: areaName, label: areaLabel }),
|
|
});
|
|
|
|
// Add a content widget via API
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}/widgets`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ type: "content", title: "Test Content Widget" }),
|
|
});
|
|
|
|
// Navigate to widgets page
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Area should be visible
|
|
await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 });
|
|
|
|
// Widget should be visible in the area
|
|
await expect(page.locator("text=Test Content Widget").first()).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Clean up
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, {
|
|
method: "DELETE",
|
|
headers,
|
|
}).catch(() => {});
|
|
});
|
|
|
|
test("deletes a widget from an area", async ({ admin, page }) => {
|
|
const areaName = `e2e-del-widget-${Date.now()}`;
|
|
const areaLabel = "Delete Widget Area";
|
|
|
|
// Create area and widget via API
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ name: areaName, label: areaLabel }),
|
|
});
|
|
|
|
const widgetRes = await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}/widgets`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ type: "content", title: "Widget To Delete" }),
|
|
});
|
|
const widgetData: any = await widgetRes.json();
|
|
const widgetTitle = widgetData.data?.widget?.title ?? "Widget To Delete";
|
|
|
|
// Navigate to widgets page
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Widget should be visible
|
|
await expect(page.locator(`text=${widgetTitle}`).first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click the delete button on the widget
|
|
const deleteButton = page.getByRole("button", { name: `Delete ${widgetTitle}` });
|
|
if (await deleteButton.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await deleteButton.click();
|
|
await admin.waitForLoading();
|
|
|
|
// Widget should disappear
|
|
await expect(page.locator(`text=${widgetTitle}`).first()).not.toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
}
|
|
|
|
// Clean up area
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, {
|
|
method: "DELETE",
|
|
headers,
|
|
}).catch(() => {});
|
|
});
|
|
});
|
|
|
|
test.describe("Delete Widget Area", () => {
|
|
test("deletes a widget area with confirmation", async ({ admin, page }) => {
|
|
const areaName = `e2e-del-area-${Date.now()}`;
|
|
const areaLabel = "Area To Delete";
|
|
|
|
// Create area via API
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ name: areaName, label: areaLabel }),
|
|
});
|
|
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
// Area should be visible
|
|
await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click the delete button on the area header
|
|
const deleteAreaButton = page.getByRole("button", {
|
|
name: `Delete ${areaLabel} widget area`,
|
|
});
|
|
await deleteAreaButton.click();
|
|
|
|
// ConfirmDialog should appear
|
|
const confirmDialog = page.getByRole("dialog", { name: "Delete Widget Area" });
|
|
await expect(confirmDialog).toBeVisible({ timeout: 5000 });
|
|
|
|
// Confirm deletion
|
|
await confirmDialog.getByRole("button", { name: "Delete" }).click();
|
|
|
|
// Area should disappear
|
|
await expect(page.locator("h3", { hasText: areaLabel })).not.toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
});
|
|
|
|
test("cancel delete keeps the widget area", async ({ admin, page }) => {
|
|
const areaName = `e2e-keep-area-${Date.now()}`;
|
|
const areaLabel = "Area To Keep";
|
|
|
|
// Create area via API
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ name: areaName, label: areaLabel }),
|
|
});
|
|
|
|
await admin.goto("/widgets");
|
|
await admin.waitForShell();
|
|
await admin.waitForLoading();
|
|
|
|
await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click delete
|
|
await page.getByRole("button", { name: `Delete ${areaLabel} widget area` }).click();
|
|
|
|
// Dialog appears
|
|
const confirmDialog = page.getByRole("dialog", { name: "Delete Widget Area" });
|
|
await expect(confirmDialog).toBeVisible({ timeout: 5000 });
|
|
|
|
// Cancel
|
|
await confirmDialog.getByRole("button", { name: "Cancel" }).click();
|
|
|
|
// Dialog closes
|
|
await expect(confirmDialog).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Area should still be there
|
|
await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible();
|
|
|
|
// Clean up
|
|
await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, {
|
|
method: "DELETE",
|
|
headers,
|
|
}).catch(() => {});
|
|
});
|
|
});
|
|
});
|