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>
204 lines
6.5 KiB
TypeScript
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),
|
|
});
|
|
});
|
|
});
|