Files
ObsiGate/tests/e2e/split-view.spec.js
T
bruno 38569c2f9f
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 16s
test: I2 + I3 — 46 tests d'intégration + suite E2E Playwright
I2 (test_pane_manager.py): 46 nouveaux tests
  - Module structure: 10 tests (exports, methods, factory, max panes)
  - Data flow: 8 tests (drag format, tree format, JSON, migration, localStorage)
  - DOM flow: 7 tests (grid, IDs, lazy render, collapse, empty pane, transfer)
  - Keyboard: 6 tests (all shortcuts + preventDefault counts)
  - Context menu: 4 tests (split, move-to, close actions)
  - CSS: 7 tests (grid, container, resize, drop zone, buttons, responsive)
  - HTML/App: 5 tests (wrapper, tab-bar, modules, imports)
  - Palette: 3 tests (commands, reset, category)
  - Editor lock: 2 tests
  - Dashboard: 1 test (TabManager vs openFile)

I3 (tests/e2e/): suite Playwright complète
  - package.json, playwright.config.js, split-view.spec.js
  - 12 test specs: basic ops, tab drag, tree drag, persistence, regression, keyboard nav
  - Exécution: cd tests/e2e && npm install && npx playwright test

Total: 353 tests passent (307 + 46)
2026-07-28 10:27:15 -04:00

431 lines
15 KiB
JavaScript

/**
* E2E tests for ObsiGate Split View (#75).
*
* Prerequisites:
* cd tests/e2e && npm install && npx playwright install chromium
*
* Run:
* OBSIGATE_URL=http://192.168.30.101:2020 npx playwright test
* OBSIGATE_URL=http://localhost:2021 npx playwright test --headed
*/
import { test, expect } from '@playwright/test';
const CREDS = {
username: process.env.OBSIGATE_USER || 'admin',
password: process.env.OBSIGATE_PASS || 'test123',
};
async function login(page) {
await page.goto('/');
// Wait for login screen
const loginForm = page.locator('#login-screen');
await expect(loginForm).toBeVisible({ timeout: 5000 }).catch(() => {
// Already logged in (session cookie)
});
if (await loginForm.isVisible()) {
await page.fill('#login-username', CREDS.username);
await page.fill('#login-password', CREDS.password);
await page.click('#login-btn');
await page.waitForSelector('#app:not(.hidden)', { timeout: 10000 });
}
}
async function openFile(page, vault, filePath) {
// Use URL hash navigation
await page.goto(`/#/file/${encodeURIComponent(vault)}/${encodeURIComponent(filePath)}`);
await page.waitForTimeout(500);
}
async function openFileViaSidebar(page, vault, filePath) {
// Click the file in the tree sidebar
const treeItem = page.locator(`.tree-item[data-vault="${vault}"][data-path="${filePath}"]`);
if (await treeItem.isVisible()) {
await treeItem.click();
await page.waitForTimeout(500);
}
}
// =============================================================================
// Basic split view tests
// =============================================================================
test.describe('Split View — Basic Operations', () => {
test.beforeEach(async ({ page }) => {
await login(page);
});
test('opens single file in tab', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
// Tab should be visible
const tabBar = page.locator('#tab-bar');
await expect(tabBar).toBeVisible();
const tabs = page.locator('.tab-item');
await expect(tabs).toHaveCount(1);
// Content area should show the file
const content = page.locator('#content-area');
await expect(content).not.toBeEmpty();
});
test('opens two files and shows two tabs', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await page.locator('#header-logo').click(); // go home
await page.waitForTimeout(300);
await openFile(page, 'TestVault', 'note2.md');
const tabs = page.locator('.tab-item');
await expect(tabs).toHaveCount(2);
});
test('split right via keyboard shortcut', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
// Trigger split right
await page.keyboard.press('Control+Alt+\\');
// Wait for pane grid to appear
await page.waitForSelector('.pane-grid', { timeout: 3000 });
const panes = page.locator('.pane-container');
await expect(panes).toHaveCount(2);
// Both panes should have content areas
const contentAreas = page.locator('.content-area');
await expect(contentAreas).toHaveCount(2);
});
test('split down via keyboard shortcut', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
await page.keyboard.press('Control+Alt+Shift+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
const panes = page.locator('.pane-container');
await expect(panes).toHaveCount(2);
});
test('split right via context menu', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
// Right-click on tab to open context menu
const tab = page.locator('.tab-item').first();
await tab.click({ button: 'right' });
// Click "Diviser à droite"
const menuItem = page.locator('#tab-context-menu .context-menu-item', { hasText: 'Diviser à droite' });
await expect(menuItem).toBeVisible();
await menuItem.click();
await page.waitForSelector('.pane-grid', { timeout: 3000 });
const panes = page.locator('.pane-container');
await expect(panes).toHaveCount(2);
});
test('closes active pane via keyboard', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Close active pane
await page.keyboard.press('Control+Alt+w');
await page.waitForTimeout(500);
// Should be back to single pane
const panes = page.locator('.pane-container');
await expect(panes).toHaveCount(0); // collapsed to single
});
});
// =============================================================================
// Tab drag and drop tests
// =============================================================================
test.describe('Split View — Tab Drag & Drop', () => {
test.beforeEach(async ({ page }) => {
await login(page);
});
test('drag tab from pane 0 to pane 1', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Get first tab
const sourceTab = page.locator('.tab-item').first();
// Get target pane's tab bar
const targetPane = page.locator('.pane-container').nth(1);
const targetTabBar = targetPane.locator('.tab-bar');
// Drag tab to target
const sourceBox = await sourceTab.boundingBox();
const targetBox = await targetTabBar.boundingBox();
if (sourceBox && targetBox) {
await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2);
await page.mouse.down();
await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y + targetBox.height / 2, { steps: 10 });
await page.mouse.up();
}
await page.waitForTimeout(500);
// Source pane should have 1 tab, target should have 1 tab
const pane0Tabs = page.locator('.pane-container').nth(0).locator('.tab-item');
const pane1Tabs = page.locator('.pane-container').nth(1).locator('.tab-item');
await expect(pane0Tabs).toHaveCount(1);
await expect(pane1Tabs).toHaveCount(1);
});
test('drag tab to right edge creates split', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
const sourceTab = page.locator('.tab-item').first();
const contentArea = page.locator('#content-area');
const tabBox = await sourceTab.boundingBox();
const areaBox = await contentArea.boundingBox();
if (tabBox && areaBox) {
// Start drag on tab
await page.mouse.move(tabBox.x + tabBox.width / 2, tabBox.y + tabBox.height / 2);
await page.mouse.down();
// Move to right 80% of content area
await page.mouse.move(
areaBox.x + areaBox.width * 0.85,
areaBox.y + areaBox.height / 2,
{ steps: 15 }
);
// Wait for drop zone indicator
await page.waitForTimeout(300);
await page.mouse.up();
}
await page.waitForSelector('.pane-grid', { timeout: 3000 });
const panes = page.locator('.pane-container');
await expect(panes).toHaveCount(2);
});
test('drag from tree to pane opens file there', async ({ page }) => {
// First create split
await openFile(page, 'TestVault', 'note1.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Find a tree item
const treeItem = page.locator('.tree-item[data-path="note2.md"]').first();
if (await treeItem.isVisible()) {
// Drag from tree to pane 1's content area
const targetPane = page.locator('.pane-container').nth(1);
const targetContent = targetPane.locator('.content-area');
const treeBox = await treeItem.boundingBox();
const targetBox = await targetContent.boundingBox();
if (treeBox && targetBox) {
await page.mouse.move(treeBox.x + treeBox.width / 2, treeBox.y + treeBox.height / 2);
await page.mouse.down();
await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y + targetBox.height / 2, { steps: 10 });
await page.mouse.up();
await page.waitForTimeout(500);
}
}
// Pane 1 should have a tab now
const pane1Tabs = page.locator('.pane-container').nth(1).locator('.tab-item');
const count = await pane1Tabs.count();
expect(count).toBeGreaterThanOrEqual(1);
});
});
// =============================================================================
// Persistence tests
// =============================================================================
test.describe('Split View — Persistence', () => {
test.beforeEach(async ({ page }) => {
await login(page);
// Clear any saved pane state
await page.evaluate(() => localStorage.removeItem('obsigate-panes'));
});
test('split state persists across page reload', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Verify localStorage has pane data
const saved = await page.evaluate(() => localStorage.getItem('obsigate-panes'));
expect(saved).toBeTruthy();
const data = JSON.parse(saved);
expect(data.panes).toHaveLength(2);
// Reload
await page.reload();
await login(page);
await page.waitForTimeout(1000);
// Should restore split
const restored = await page.evaluate(() => localStorage.getItem('obsigate-panes'));
if (restored) {
const restoredData = JSON.parse(restored);
expect(restoredData.panes.length).toBeGreaterThanOrEqual(2);
}
});
test('reset panes command removes localStorage', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Open command palette and reset
await page.keyboard.press('Control+Shift+ '); // Ctrl+Shift+Space
await page.waitForTimeout(300);
// Type "réinitialiser" and select
const input = page.locator('#cp-input');
if (await input.isVisible()) {
await input.fill('réinitialiser');
await page.waitForTimeout(200);
await page.keyboard.press('Enter');
}
// localStorage should be cleared
const cleared = await page.evaluate(() => localStorage.getItem('obsigate-panes'));
expect(cleared).toBeNull();
});
});
// =============================================================================
// Regression tests
// =============================================================================
test.describe('Split View — Regression', () => {
test.beforeEach(async ({ page }) => {
await login(page);
await page.evaluate(() => localStorage.removeItem('obsigate-panes'));
});
test('single pane mode works identically to before', async ({ page }) => {
// In single-pane mode, everything should use #content-area
await openFile(page, 'TestVault', 'note1.md');
const contentArea = page.locator('#content-area');
await expect(contentArea).toBeVisible();
await expect(contentArea).not.toBeEmpty();
// No pane grid should exist
const paneGrid = page.locator('.pane-grid');
await expect(paneGrid).toHaveCount(0);
// Tab bar should show #tab-list
const tabList = page.locator('#tab-list');
await expect(tabList).toBeVisible();
});
test('closing last tab shows dashboard', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
// Close tab via middle-click
const tab = page.locator('.tab-item').first();
await tab.click({ button: 'middle' });
await page.waitForTimeout(500);
// Dashboard should be visible
const dashboard = page.locator('#dashboard-home');
await expect(dashboard).toBeVisible();
// Tab bar should be hidden
const tabBar = page.locator('#tab-bar');
await expect(tabBar).toBeHidden();
});
test('bookmark click opens file in tab system', async ({ page }) => {
// First bookmark a file for the test
await openFile(page, 'TestVault', 'note1.md');
// Go home
await page.locator('#header-logo').click();
await page.waitForTimeout(500);
// If bookmark card exists, click it
const bookmarkGrid = page.locator('#dashboard-bookmarks-grid');
if (await bookmarkGrid.isVisible()) {
const cards = bookmarkGrid.locator('.dashboard-card');
const count = await cards.count();
if (count > 0) {
await cards.first().click();
await page.waitForTimeout(500);
// Should have opened in a tab
const tabs = page.locator('.tab-item');
await expect(tabs).toHaveCount(1);
}
}
});
});
// =============================================================================
// Keyboard navigation tests
// =============================================================================
test.describe('Split View — Keyboard Navigation', () => {
test.beforeEach(async ({ page }) => {
await login(page);
await page.evaluate(() => localStorage.removeItem('obsigate-panes'));
});
test('navigate between panes with Ctrl+Alt+Arrow', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Pane 0 should be active initially
const pane0 = page.locator('.pane-container').nth(0);
await expect(pane0).toHaveClass(/pane-active/);
// Navigate right
await page.keyboard.press('Control+Alt+ArrowRight');
await page.waitForTimeout(300);
// Pane 1 should now be active
const pane1 = page.locator('.pane-container').nth(1);
await expect(pane1).toHaveClass(/pane-active/);
// Navigate left back
await page.keyboard.press('Control+Alt+ArrowLeft');
await page.waitForTimeout(300);
await expect(pane0).toHaveClass(/pane-active/);
});
test('close other panes with Ctrl+Shift+Alt+W', async ({ page }) => {
await openFile(page, 'TestVault', 'note1.md');
await openFile(page, 'TestVault', 'note2.md');
await page.keyboard.press('Control+Alt+\\');
await page.waitForSelector('.pane-grid', { timeout: 3000 });
// Create a third pane
await page.keyboard.press('Control+Alt+\\');
await page.waitForTimeout(500);
let panes = page.locator('.pane-container');
const initialCount = await panes.count();
expect(initialCount).toBeGreaterThanOrEqual(2);
// Close other panes
await page.keyboard.press('Control+Shift+Alt+w');
await page.waitForTimeout(500);
// Should be back to single pane
panes = page.locator('.pane-container');
await expect(panes).toHaveCount(0);
});
});