Files
ObsiGate/tests/e2e/excalidraw.spec.js
T

183 lines
7.3 KiB
JavaScript

/**
* E2E tests for ObsiGate Excalidraw Support (#78).
*
* Run (local):
* BASE_URL=http://localhost:2029 npx playwright test tests/e2e/excalidraw.spec.js
* BASE_URL=http://localhost:2021 npx playwright test tests/e2e/excalidraw.spec.js --headed
*/
import { test, expect } from '@playwright/test';
const BASE = process.env.BASE_URL || 'http://localhost:2029';
const CREDS = {
username: process.env.OBSIGATE_USER || 'admin',
password: process.env.OBSIGATE_PASS || 'test123',
};
async function login(page) {
await page.goto(BASE);
const loginForm = page.locator('#login-screen');
await expect(loginForm).toBeVisible({ timeout: 5000 }).catch(() => {});
if (await loginForm.isVisible()) {
await page.fill('#login-username', CREDS.username);
await page.fill('#login-password', CREDS.password);
await page.click('#login-btn');
}
// Wait until app init() resolved (all handlers bound).
await page.waitForFunction(() => window.__OBSIGATE_BOOTED === true, { timeout: 20000 });
}
async function openFile(page, vault, filePath) {
const treeItem = page.locator(`.tree-item[data-vault="${vault}"][data-path="${filePath}"]`);
if (!(await treeItem.isVisible().catch(() => false))) {
const vaultItem = page.locator(`.vault-item[data-vault="${vault}"]`).first();
if (await vaultItem.count() > 0) {
await vaultItem.click();
await page.waitForTimeout(800);
}
}
await treeItem.dblclick({ timeout: 5000 });
await page.waitForTimeout(500);
}
async function waitForExcalidrawIframe(page) {
await page.waitForSelector('.content-area iframe', { timeout: 15000 });
const iframe = page.locator('.content-area iframe').first();
await expect(iframe).toBeVisible({ timeout: 10000 });
// Give React/Excalidraw time to mount (loads React + Excalidraw from esm.sh)
await page.waitForTimeout(2500);
return iframe;
}
test.describe('Excalidraw — .excalidraw file support', () => {
test.beforeEach(async ({ page }) => {
await login(page);
await page.evaluate(() => localStorage.removeItem('obsigate-panes'));
});
test('opens .excalidraw file — iframe renders Excalidraw canvas', async ({ page }) => {
await openFile(page, 'TestVault', 'diagram.excalidraw');
const iframe = await waitForExcalidrawIframe(page);
await expect(iframe).toHaveAttribute('src', /excalidraw-editor\.html/);
// Excalidraw mounts two <canvas> layers (static + interactive) — target the first.
const canvas = page.locator('.content-area iframe').contentFrame().locator('canvas').first();
await expect(canvas).toBeVisible({ timeout: 20000 });
});
test('opens an Excalidraw-app export (collaborators as JSON object) — canvas renders', async ({ page }) => {
// Regression: files exported by the Excalidraw app / Obsidian plugin store
// `appState.collaborators` as a plain `{}` (a Map serialized to JSON).
// Passing it back to `initialData` made Excalidraw 0.18 crash with
// "e.appState.collaborators.forEach is not a function" and left the canvas
// blank (0 <canvas> in the iframe).
await openFile(page, 'TestVault', 'diagram-app-export.excalidraw');
await waitForExcalidrawIframe(page);
const frame = page.locator('.content-area iframe').contentFrame();
const canvas = frame.locator('canvas').first();
await expect(canvas).toBeVisible({ timeout: 20000 });
});
test('loads Excalidraw stylesheet — canvas is sized to the viewport (not the 2^25 cap)', async ({ page }) => {
// Regression: without `@excalidraw/excalidraw`'s stylesheet, `.excalidraw`
// has no fixed height; Excalidraw's ResizeObserver feedback loop then grows
// the canvas to its 2^25 hard cap (33554432px), which the browser cannot
// draw → the scene stays blank even though the data was loaded.
await openFile(page, 'TestVault', 'diagram.excalidraw');
await waitForExcalidrawIframe(page);
const frame = page.locator('.content-area iframe').contentFrame();
const canvas = frame.locator('canvas').first();
await expect(canvas).toBeVisible({ timeout: 20000 });
const dims = await canvas.evaluate((el) => ({ w: el.width, h: el.height }));
expect(dims.h).toBeGreaterThan(100);
expect(dims.h).toBeLessThan(32768);
const wrapH = await frame.locator('.excalidraw').first()
.evaluate((el) => parseFloat(getComputedStyle(el).height));
expect(wrapH).toBeLessThan(32768);
});
test('opens .excalidraw.md (Obsidian plugin format) — decompresses and renders', async ({ page }) => {
await openFile(page, 'TestVault', 'diagram.excalidraw.md');
const iframe = await waitForExcalidrawIframe(page);
await expect(iframe).toHaveAttribute('src', /excalidraw-editor\.html/);
const canvas = page.locator('.content-area iframe').contentFrame().locator('canvas').first();
await expect(canvas).toBeVisible({ timeout: 20000 });
});
test('theme toggle propagates to iframe without crash', async ({ page }) => {
await openFile(page, 'TestVault', 'diagram.excalidraw');
await waitForExcalidrawIframe(page);
const themeToggle = page.locator('#theme-toggle').first();
if (await themeToggle.isVisible()) {
await themeToggle.click();
await page.waitForTimeout(500);
await themeToggle.click();
}
// No crash = pass; iframe still present
await expect(page.locator('.content-area iframe').first()).toBeVisible();
});
});
test.describe('Excalidraw — Creation from UI', () => {
test.beforeEach(async ({ page }) => {
await login(page);
await page.evaluate(() => localStorage.removeItem('obsigate-panes'));
});
test('creates new .excalidraw via directory context menu (C8)', async ({ page }) => {
// Expand the vault so a directory node is visible.
const vaultItem = page.locator('.vault-item[data-vault="TestVault"]').first();
await vaultItem.click();
await page.waitForTimeout(800);
// Right-click a directory (context menu offers "Nouveau diagramme Excalidraw").
const dirItem = page.locator('.tree-item[data-vault="TestVault"][data-path="IT"]').first();
await dirItem.click({ button: 'right' });
await page.waitForSelector('#context-menu.active', { timeout: 5000 });
const excalidrawMenu = page.locator('#context-menu .context-menu-item', { hasText: 'Nouveau diagramme Excalidraw' });
await expect(excalidrawMenu).toBeVisible();
await excalidrawMenu.click();
// Modal opens with .excalidraw pre-selected.
await page.waitForSelector('.obsigate-modal-overlay.active', { timeout: 5000 });
const extSelect = page.locator('#file-ext-select');
await expect(extSelect).toHaveValue('.excalidraw');
const unique = `e2e-diagram-${Date.now()}`;
await page.fill('#file-name-input', unique);
await page.click('#file-create-btn');
// New file opens in the Excalidraw editor.
await waitForExcalidrawIframe(page);
});
});
test.describe('Excalidraw — Search integration', () => {
test.beforeEach(async ({ page }) => {
await login(page);
});
test('Excalidraw file appears in search results with text snippet', async ({ page }) => {
const searchInput = page.locator('#search-input').first();
await searchInput.fill('diagram');
await searchInput.press('Enter');
await page.waitForSelector('.search-result-item, .search-results-header', { timeout: 10000 });
const results = page.locator('.search-result-item');
expect(await results.count()).toBeGreaterThan(0);
});
});