/** * 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", }); }); });