first commit

This commit is contained in:
Matt Kane
2026-04-01 10:44:22 +01:00
commit 43fcb9a131
1789 changed files with 395041 additions and 0 deletions

305
e2e/tests/widgets.spec.ts Normal file
View File

@@ -0,0 +1,305 @@
/**
* 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(() => {});
});
});
});