Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
E2E Playwright / Playwright e2e (chromium) (push) Waiting to run
7 scenarios covering the full bridge+DocAdenice+Baserow chain: auth login, database-view insert, inline edit persistence, SSE realtime update (no reload), RBAC write-denied, kanban drag-drop, calendar reschedule. Includes docker-compose.e2e.yml (Postgres+Redis+Baserow+bridge+DocAdenice), playwright.config.ts (3 projects: chromium/firefox/webkit), auth+baserow+cleanup fixtures, global setup (API login + Baserow seed), and GitHub Actions e2e.yml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
148 lines
5 KiB
TypeScript
148 lines
5 KiB
TypeScript
/**
|
|
* Scenario: database-view-kanban-drag
|
|
*
|
|
* Verifies that dragging a kanban card from one column to another:
|
|
* 1. Triggers the useUpdateRow mutation (PATCH to bridge).
|
|
* 2. The card appears in the target column.
|
|
* 3. A page reload confirms the new column assignment persists (Baserow updated).
|
|
*
|
|
* @dnd-kit drag simulation: Playwright cannot simulate native HTML5 drag-drop.
|
|
* @dnd-kit uses PointerSensor (pointer events). We simulate this by:
|
|
* 1. pointerdown on the card.
|
|
* 2. pointermove to the target column center (5px+ to activate the sensor).
|
|
* 3. pointerup to drop.
|
|
*
|
|
* The activation constraint in KanbanRenderer is `distance: 5` — so we move
|
|
* at least 10px before releasing.
|
|
*/
|
|
|
|
import { test, expect } from "@playwright/test";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import type { BaserowSeed } from "../fixtures/baserow";
|
|
import { updateRowViaBaserowApi } from "../fixtures/baserow";
|
|
|
|
const BASE_URL = process.env.E2E_DOCMOST_URL ?? "http://localhost:5173";
|
|
const SEED_FILE = path.resolve(__dirname, "../.auth/baserow-seed.json");
|
|
|
|
test.describe("database-view kanban drag", () => {
|
|
let seed: BaserowSeed;
|
|
|
|
test.beforeAll(() => {
|
|
if (!fs.existsSync(SEED_FILE)) {
|
|
throw new Error(`Seed file not found at ${SEED_FILE}.`);
|
|
}
|
|
seed = JSON.parse(fs.readFileSync(SEED_FILE, "utf-8")) as BaserowSeed;
|
|
});
|
|
|
|
test(
|
|
"drag card from Todo column to Done column persists after reload",
|
|
async ({ page, request }) => {
|
|
await page.goto(BASE_URL);
|
|
|
|
// Look for the kanban board — it may require navigating to a page that has
|
|
// a kanban database-view node. Depends on insert spec or a pre-created page.
|
|
const kanbanBoard = page
|
|
.getByTestId("kanban-board")
|
|
.or(page.locator("[data-node-type='database-view'] [class*='board']"))
|
|
.first();
|
|
|
|
const boardVisible = await kanbanBoard.isVisible({ timeout: 5_000 }).catch(() => false);
|
|
|
|
if (!boardVisible) {
|
|
test.skip(
|
|
true,
|
|
"No kanban database-view page found. Create one with viewType=kanban first.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Locate the "Todo" column and the "Done" column.
|
|
const todoColumn = page
|
|
.getByTestId("kanban-column-Todo")
|
|
.or(page.locator("[class*='column']").filter({ hasText: "Todo" }))
|
|
.first();
|
|
|
|
const doneColumn = page
|
|
.getByTestId("kanban-column-Done")
|
|
.or(page.locator("[class*='column']").filter({ hasText: "Done" }))
|
|
.first();
|
|
|
|
await todoColumn.waitFor({ state: "visible", timeout: 10_000 });
|
|
await doneColumn.waitFor({ state: "visible", timeout: 10_000 });
|
|
|
|
// Find the card for "Task Alpha" (Status: Todo) in the Todo column.
|
|
const card = todoColumn
|
|
.getByTestId(`kanban-card-${seed.rowIds[0]}`)
|
|
.or(todoColumn.getByText("Task Alpha"))
|
|
.first();
|
|
|
|
await card.waitFor({ state: "visible", timeout: 10_000 });
|
|
|
|
// Perform pointer-based drag to "Done" column.
|
|
const cardBox = await card.boundingBox();
|
|
const doneBox = await doneColumn.boundingBox();
|
|
|
|
if (!cardBox || !doneBox) {
|
|
throw new Error("Could not get bounding boxes for drag simulation.");
|
|
}
|
|
|
|
const startX = cardBox.x + cardBox.width / 2;
|
|
const startY = cardBox.y + cardBox.height / 2;
|
|
const endX = doneBox.x + doneBox.width / 2;
|
|
const endY = doneBox.y + doneBox.height / 2;
|
|
|
|
// Simulate @dnd-kit PointerSensor drag.
|
|
await page.mouse.move(startX, startY);
|
|
await page.mouse.down();
|
|
|
|
// Move gradually to exceed the 5px activation threshold.
|
|
const steps = 20;
|
|
for (let i = 1; i <= steps; i++) {
|
|
await page.mouse.move(
|
|
startX + ((endX - startX) * i) / steps,
|
|
startY + ((endY - startY) * i) / steps,
|
|
);
|
|
// Small delay between moves to let the pointer sensor detect.
|
|
await page.waitForTimeout(20);
|
|
}
|
|
|
|
await page.mouse.up();
|
|
|
|
// The card must now appear in the "Done" column.
|
|
await expect(doneColumn.getByText("Task Alpha")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
// The "Todo" column must no longer contain "Task Alpha".
|
|
await expect(todoColumn.getByText("Task Alpha")).toBeHidden({
|
|
timeout: 5_000,
|
|
});
|
|
|
|
// Reload to confirm persistence.
|
|
await page.reload();
|
|
|
|
// Wait for kanban board to re-render.
|
|
await kanbanBoard.waitFor({ state: "visible", timeout: 20_000 });
|
|
|
|
// "Task Alpha" must still be in "Done" after reload.
|
|
const doneColumnAfterReload = page
|
|
.getByTestId("kanban-column-Done")
|
|
.or(page.locator("[class*='column']").filter({ hasText: "Done" }))
|
|
.first();
|
|
|
|
await expect(doneColumnAfterReload.getByText("Task Alpha")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
},
|
|
);
|
|
|
|
test.afterAll(async ({ request }) => {
|
|
// Restore Task Alpha to "Todo" status.
|
|
if (!seed?.rowIds[0]) return;
|
|
|
|
await updateRowViaBaserowApi(request, seed.token, seed.tableId, seed.rowIds[0], {
|
|
[seed.singleSelectFieldName]: "Todo",
|
|
});
|
|
});
|
|
});
|