Wiki/e2e/tests/database-view-calendar-reschedule.spec.ts
Corentin JOGUET e9695450ef
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
test(e2e): add Playwright cross-stack tests for R3.1.e database-view
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>
2026-05-08 00:37:23 +02:00

204 lines
6.5 KiB
TypeScript

/**
* Scenario: database-view-calendar-reschedule
*
* Verifies that dragging an event on the FullCalendar to a new date:
* 1. Triggers the useUpdateRow mutation (PATCH to bridge via onEventDrop).
* 2. The event appears on the new date.
* 3. A page reload confirms the new date persists (Baserow updated).
*
* FullCalendar event drag: FullCalendar uses its own drag implementation on
* top of the interaction plugin. The rendered events have a title attribute
* and can be dragged via mouse simulation. We:
* 1. Locate the event element by its title text.
* 2. Get its bounding box.
* 3. Locate the target date cell.
* 4. Simulate mouse drag from event to target date cell.
*
* The test targets "Task Alpha" which is seeded to today+1. We move it to today+8.
*/
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");
/**
* Format a date as "YYYY-MM-DD" (ISO 8601 date only).
*/
function isoDate(date: Date): string {
return date.toISOString().slice(0, 10);
}
/**
* Add days to a date and return a new Date.
*/
function addDays(date: Date, days: number): Date {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
test.describe("database-view calendar reschedule", () => {
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 calendar event to new date persists after reload",
async ({ page, request }) => {
await page.goto(BASE_URL);
// Wait for the calendar renderer.
const calendarWrapper = page
.getByTestId("calendar-renderer")
.or(page.locator("[data-node-type='database-view'] .fc"))
.first();
const calendarVisible = await calendarWrapper
.isVisible({ timeout: 5_000 })
.catch(() => false);
if (!calendarVisible) {
test.skip(
true,
"No calendar database-view page found. Create one with viewType=calendar first.",
);
return;
}
// Wait for FullCalendar to fully render (events loaded).
await calendarWrapper.waitFor({ state: "visible", timeout: 15_000 });
// Locate the "Task Alpha" event on the calendar.
const eventEl = calendarWrapper
.locator(".fc-event")
.filter({ hasText: "Task Alpha" })
.first();
await eventEl.waitFor({ state: "visible", timeout: 10_000 });
// Determine the target date: today + 8 days (far enough from today+1 to
// land on a different date cell in month view).
const now = new Date();
const targetDate = addDays(now, 8);
const targetDateStr = isoDate(targetDate);
// FullCalendar renders day cells with data-date attributes in month view.
const targetCell = calendarWrapper
.locator(`[data-date="${targetDateStr}"]`)
.first();
const targetVisible = await targetCell
.isVisible({ timeout: 5_000 })
.catch(() => false);
if (!targetVisible) {
// Target date is outside the current month view — navigate forward.
const nextBtn = calendarWrapper
.locator(".fc-next-button, button[aria-label*='next']")
.first();
if (await nextBtn.isVisible()) {
await nextBtn.click();
await page.waitForTimeout(500);
}
const targetCellAfterNav = calendarWrapper
.locator(`[data-date="${targetDateStr}"]`)
.first();
const visibleAfterNav = await targetCellAfterNav
.isVisible({ timeout: 5_000 })
.catch(() => false);
if (!visibleAfterNav) {
test.skip(
true,
`Target date cell ${targetDateStr} not found in current or next month view.`,
);
return;
}
}
// Get bounding boxes.
const eventBox = await eventEl.boundingBox();
const targetBox = await targetCell.boundingBox();
if (!eventBox || !targetBox) {
throw new Error("Could not get bounding boxes for calendar drag.");
}
const startX = eventBox.x + eventBox.width / 2;
const startY = eventBox.y + eventBox.height / 2;
const endX = targetBox.x + targetBox.width / 2;
const endY = targetBox.y + targetBox.height / 2;
// Simulate drag — FullCalendar's interaction plugin listens on mousedown/move/up.
await page.mouse.move(startX, startY);
await page.mouse.down();
// Move gradually to avoid snap-back.
const steps = 25;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(
startX + ((endX - startX) * i) / steps,
startY + ((endY - startY) * i) / steps,
);
await page.waitForTimeout(15);
}
await page.mouse.up();
// Wait for the event to appear on the target date.
await expect(
targetCell.locator(".fc-event").filter({ hasText: "Task Alpha" }),
).toBeVisible({ timeout: 15_000 });
// Reload to confirm persistence.
await page.reload();
await calendarWrapper.waitFor({ state: "visible", timeout: 20_000 });
// Re-locate target cell after reload.
const targetCellAfterReload = calendarWrapper
.locator(`[data-date="${targetDateStr}"]`)
.first();
// Navigate forward if needed.
const targetVisibleAfterReload = await targetCellAfterReload
.isVisible({ timeout: 3_000 })
.catch(() => false);
if (!targetVisibleAfterReload) {
const nextBtn = calendarWrapper
.locator(".fc-next-button, button[aria-label*='next']")
.first();
if (await nextBtn.isVisible()) await nextBtn.click();
await page.waitForTimeout(500);
}
await expect(
calendarWrapper
.locator(`[data-date="${targetDateStr}"] .fc-event`)
.filter({ hasText: "Task Alpha" }),
).toBeVisible({ timeout: 15_000 });
},
);
test.afterAll(async ({ request }) => {
// Restore Task Alpha's date to today+1.
if (!seed?.rowIds[0]) return;
const tomorrow = addDays(new Date(), 1);
await updateRowViaBaserowApi(request, seed.token, seed.tableId, seed.rowIds[0], {
[seed.dateFieldName]: isoDate(tomorrow),
});
});
});