""" Tests for the Multi-Pane Split View (#75). Since PaneManager is a frontend JS module, these tests verify: 1. Backend integration: all file/sidebar/vault APIs still work (no regression) 2. Static file serving: pane-manager.js is served correctly 3. CSS: style.css includes pane grid classes 4. HTML: index.html includes the content-wrapper structure needed for panes """ import pytest import re from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent class TestPaneManagerStaticAssets: """Verify pane-manager.js, style.css, and index.html are properly structured.""" def test_pane_manager_js_exists(self): """PaneManager module file is present.""" path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js" assert path.exists(), f"pane-manager.js not found at {path}" def test_pane_manager_js_exports(self): """PaneManager exports the expected API.""" path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js" content = path.read_text() assert "export default PaneManager" in content assert "window.PaneManager = PaneManager" in content def test_pane_manager_has_split_methods(self): """PaneManager has splitRight and splitDown methods.""" path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js" content = path.read_text() assert "splitRight()" in content assert "splitDown()" in content assert "closePane(" in content assert "closeActivePane()" in content assert "isSplit()" in content assert "getActiveContentArea()" in content def test_pane_manager_has_keyboard_shortcuts(self): """Keyboard shortcuts are registered.""" path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js" content = path.read_text() assert "ctrlKey" in content assert '"\\\\"' in content or "'\\\\'" in content # backslash key def test_style_css_has_pane_grid_classes(self): """style.css includes pane grid CSS classes.""" path = PROJECT_ROOT / "frontend" / "style.css" content = path.read_text() assert ".pane-grid" in content assert ".pane-container" in content assert ".pane-resize-handle" in content assert ".pane-active" in content def test_style_css_has_responsive_split(self): """Responsive rules disable split below 768px.""" path = PROJECT_ROOT / "frontend" / "style.css" content = path.read_text() assert "@media (max-width: 768px)" in content assert ".pane-grid" in content def test_viewer_uses_pane_aware_helper(self): """viewer.js has getContentArea() helper and uses it.""" path = PROJECT_ROOT / "frontend" / "js" / "viewer.js" content = path.read_text() assert "function getContentArea()" in content assert "window.PaneManager" in content # Should NOT have direct document.getElementById("content-area") except in fallback direct_refs = [l for l in content.split("\n") if 'document.getElementById("content-area")' in l and "fallback" not in l.lower()] # Only the fallback inside getContentArea should remain assert len(direct_refs) <= 2, f"Too many direct content-area refs: {direct_refs}" def test_ui_tab_manager_has_delegation(self): """TabManager in ui.js delegates to getActiveTabManager() when split.""" path = PROJECT_ROOT / "frontend" / "js" / "ui.js" content = path.read_text() assert "getActiveTabManager" in content assert "PaneManager.isSplit()" in content # Must have delegation in entry-point methods assert "window.getActiveTabManager().openPreview" in content assert "window.getActiveTabManager().openPersistent" in content assert "window.getActiveTabManager().close(" in content def test_ui_context_menu_has_split_actions(self): """Tab context menu includes 'Diviser à droite' and 'Diviser en bas'.""" path = PROJECT_ROOT / "frontend" / "js" / "ui.js" content = path.read_text() assert "Diviser à droite" in content assert "Diviser en bas" in content assert "Fermer le panneau" in content assert 'data-action="splitRight"' in content assert 'data-action="splitDown"' in content def test_html_has_content_wrapper(self): """index.html has the content-wrapper structure needed for panes.""" path = PROJECT_ROOT / "frontend" / "index.html" content = path.read_text() assert 'class="content-wrapper"' in content assert 'id="content-area"' in content assert 'id="tab-bar"' in content assert 'id="tab-list"' in content def test_html_uses_es_modules(self): """index.html uses ES module scripts for the frontend modules.""" path = PROJECT_ROOT / "frontend" / "index.html" content = path.read_text() # Should load the ES module app.js orchestrator assert 'type="module"' in content def test_brace_balance_pane_manager(self): """PaneManager JS has balanced braces, parens, brackets.""" path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js" content = path.read_text() for char, name in [("{", "braces"), ("(", "parens"), ("[", "brackets")]: close_map = {"{": "}", "(": ")", "[": "]"} assert content.count(char) == content.count(close_map[char]), \ f"Unbalanced {name}: {content.count(char)} opens vs {content.count(close_map[char])} closes" def test_brace_balance_viewer(self): """viewer.js has balanced braces, parens, brackets.""" path = PROJECT_ROOT / "frontend" / "js" / "viewer.js" content = path.read_text() for char, name in [("{", "braces"), ("(", "parens"), ("[", "brackets")]: close_map = {"{": "}", "(": ")", "[": "]"} assert content.count(char) == content.count(close_map[char]), \ f"Unbalanced {name}: {content.count(char)} opens vs {content.count(close_map[char])} closes" def test_brace_balance_ui(self): """ui.js has balanced braces, parens, brackets.""" path = PROJECT_ROOT / "frontend" / "js" / "ui.js" content = path.read_text() for char, name in [("{", "braces"), ("(", "parens"), ("[", "brackets")]: close_map = {"{": "}", "(": ")", "[": "]"} assert content.count(char) == content.count(close_map[char]), \ f"Unbalanced {name}: {content.count(char)} opens vs {content.count(close_map[char])} closes" class TestBackendRegression: """Verify backend APIs still work with frontend changes.""" def test_file_view_endpoint(self, client): """GET /api/file/{vault} still returns expected fields.""" resp = client.get("/api/file/TestVault", params={"path": "note1.md"}) assert resp.status_code == 200 data = resp.json() assert "title" in data or "path" in data def test_vault_list_endpoint(self, client): """GET /api/vaults still works.""" resp = client.get("/api/vaults") assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) def test_search_endpoint(self, client): """GET /api/search still works.""" resp = client.get("/api/search?q=test") assert resp.status_code == 200 data = resp.json() assert "results" in data def test_sidebar_tree_endpoint(self, client): """GET /api/sidebar/tree/{vault} still works.""" resp = client.get("/api/sidebar/tree/vault1") assert resp.status_code == 200 def test_static_css_served(self, client): """style.css is served correctly.""" resp = client.get("/static/style.css") assert resp.status_code == 200 assert "text/css" in resp.headers.get("content-type", "") def test_static_js_served(self, client): """app.js (ES module) is served correctly.""" resp = client.get("/static/js/app.js") assert resp.status_code == 200 assert "javascript" in resp.headers.get("content-type", "").lower() def test_pane_manager_js_served(self, client): """pane-manager.js is served as static asset.""" resp = client.get("/static/js/pane-manager.js") assert resp.status_code == 200 assert "javascript" in resp.headers.get("content-type", "").lower() assert "PaneManager" in resp.text def test_index_html_served(self, client): """index.html is served and contains content-wrapper.""" resp = client.get("/") # May redirect or return 200 depending on auth assert resp.status_code in (200, 302, 401)