from __future__ import annotations import sys import unittest from pathlib import Path from fastapi.staticfiles import StaticFiles from starlette.routing import Mount sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from backend.app.main import app class UiSmokeGoldenTest(unittest.TestCase): def _ui_mount(self) -> Mount: for route in app.routes: if isinstance(route, Mount) and route.path == "/ui": return route self.fail("Expected /ui mount to be registered") def test_ui_mount_and_index_contains_expected_panels(self) -> None: mount = self._ui_mount() self.assertIsInstance(mount.app, StaticFiles) index_path = Path(mount.app.directory) / "index.html" self.assertTrue(index_path.exists()) body = index_path.read_text(encoding="utf-8") self.assertIn('id="workspace"', body) self.assertIn('id="footer-bar"', body) self.assertIn('id="title-zone-actions"', body) self.assertIn('id="status"', body) self.assertIn('id="theme-toggle"', body) self.assertIn('id="theme-toggle-icon"', body) self.assertIn('id="left-pane"', body) self.assertIn('id="right-pane"', body) self.assertIn('id="left-items"', body) self.assertIn('id="right-items"', body) self.assertIn('id="function-bar"', body) self.assertIn('id="settings-btn"', body) self.assertIn('id="rename-btn"', body) self.assertIn('id="view-btn"', body) self.assertIn('id="edit-btn"', body) self.assertIn("F1", body) self.assertIn("F2", body) self.assertIn("F3", body) self.assertIn("F4", body) self.assertIn("F5", body) self.assertIn("F6", body) self.assertIn("F7", body) self.assertIn("F8", body) self.assertIn('id="viewer-modal"', body) self.assertIn('id="video-modal"', body) self.assertIn('id="pdf-modal"', body) self.assertIn('id="pdf-frame"', body) self.assertIn('id="pdf-close-btn"', body) self.assertIn('id="video-player"', body) self.assertIn('id="video-close-btn"', body) self.assertIn('id="settings-modal"', body) self.assertIn('id="search-modal"', body) self.assertIn('id="search-input"', body) self.assertIn('id="search-results"', body) self.assertIn('id="info-modal"', body) self.assertIn('id="rename-popup"', body) self.assertIn('id="rename-input"', body) self.assertIn('id="rename-apply-btn"', body) self.assertIn('id="settings-general-tab"', body) self.assertIn('id="settings-logs-tab"', body) self.assertIn('id="settings-show-thumbnails"', body) self.assertIn("Show thumbnails", body) self.assertIn('id="settings-startup-path-left"', body) self.assertIn('id="settings-startup-path-right"', body) self.assertIn("Preferred startup path (left)", body) self.assertIn("Preferred startup path (right)", body) self.assertIn('id="settings-general-save-btn"', body) self.assertIn('id="settings-logs-list"', body) self.assertIn('id="viewer-content"', body) self.assertIn('id="editor-modal"', body) self.assertIn('id="editor-content"', body) self.assertIn('id="editor-save-btn"', body) self.assertIn('id="editor-cancel-btn"', body) self.assertIn('id="move-popup"', body) self.assertIn('id="move-input"', body) self.assertIn(">Move", body) self.assertIn(">Target path", body) self.assertIn('id="batch-move-popup"', body) self.assertIn('id="batch-move-apply-btn"', body) self.assertIn('id="mkdir-btn"', body) self.assertIn('id="copy-btn"', body) self.assertIn('id="move-btn"', body) self.assertIn('id="rename-btn"', body) self.assertIn('id="delete-btn"', body) self.assertIn('id="left-breadcrumbs"', body) self.assertIn('id="right-breadcrumbs"', body) self.assertIn('id="wildcard-popup"', body) self.assertIn('id="wildcard-pattern-input"', body) self.assertNotIn('id="search-btn"', body) self.assertNotIn('id="info-btn"', body) self.assertNotIn('id="bookmarks-panel"', body) self.assertNotIn('id="tasks-panel"', body) ordered_ids = [ 'id="settings-btn"', 'id="rename-btn"', 'id="view-btn"', 'id="edit-btn"', 'id="copy-btn"', 'id="move-btn"', 'id="mkdir-btn"', 'id="delete-btn"', ] positions = [body.index(marker) for marker in ordered_ids] self.assertEqual(positions, sorted(positions)) def test_ui_static_assets_are_present_and_mapped(self) -> None: mount = self._ui_mount() static_root = Path(mount.app.directory) self.assertTrue((static_root / "app.js").exists()) self.assertTrue((static_root / "style.css").exists()) app_js = (static_root / "app.js").read_text(encoding="utf-8") self.assertIn('currentPath: "/Volumes"', app_js) self.assertIn('const THEME_STORAGE_KEY = "webmanager-theme"', app_js) self.assertIn("document.documentElement.dataset.theme", app_js) self.assertIn('document.getElementById("theme-toggle").onclick = toggleTheme;', app_js) self.assertIn('document.getElementById("settings-btn").onclick = () => openSettings("general");', app_js) self.assertIn('async function loadSettings()', app_js) self.assertIn('await loadSettings();', app_js) self.assertIn('settings.showThumbnailsInput.onchange = handleShowThumbnailsChange;', app_js) self.assertIn('settings.generalSaveButton.onclick = handlePreferredStartupPathSave;', app_js) self.assertIn('preferredStartupPathLeft', app_js) self.assertIn('preferredStartupPathRight', app_js) self.assertIn('preferred_startup_path_left', app_js) self.assertIn('preferred_startup_path_right', app_js) self.assertIn('paneState("left").currentPath = settingsState.preferredStartupPathLeft || "/Volumes";', app_js) self.assertIn('paneState("right").currentPath = settingsState.preferredStartupPathRight || "/Volumes";', app_js) self.assertIn('"/api/settings"', app_js) self.assertIn('`/api/files/thumbnail?', app_js) self.assertIn("function iconTypeForEntry(entry)", app_js) self.assertIn("function mediaIconSvg(type)", app_js) self.assertIn('const iconType = iconTypeForEntry(entry);', app_js) self.assertIn('function createMediaSlot(entry)', app_js) self.assertNotIn("select-marker", app_js) self.assertIn('function openSearch()', app_js) self.assertIn('async function submitSearch()', app_js) self.assertIn('async function openInfo()', app_js) self.assertIn('document.getElementById("info-modal")', app_js) self.assertIn("`/api/files/info?", app_js) self.assertIn('document.getElementById("search-input")', app_js) self.assertIn("`/api/search?", app_js) self.assertIn('event.key.toLowerCase() === "f"', app_js) self.assertIn('(event.metaKey || event.ctrlKey)', app_js) self.assertIn('const isInfoShortcut = event.key === "Enter"', app_js) self.assertIn('if (event.key === "F1") {', app_js) self.assertIn('if (event.key === "F2") {', app_js) self.assertIn('function openSettings(tab = "general")', app_js) self.assertIn('function openRenamePopup()', app_js) self.assertIn('document.getElementById("rename-btn").onclick = openRenamePopup;', app_js) self.assertIn('return triggerActionButton("rename-btn");', app_js) self.assertIn('function openVideoViewer()', app_js) self.assertIn('function openPdfViewer()', app_js) self.assertIn('document.getElementById("pdf-modal")', app_js) self.assertIn("`/api/files/pdf?", app_js) self.assertIn('if (isPdfSelection(selected)) {', app_js) self.assertIn('video.player.src = streamUrl;', app_js) self.assertIn('document.getElementById("video-close-btn")', app_js) self.assertIn('row.ondblclick = (ev) => {', app_js) self.assertIn('function openMovePopup()', app_js) self.assertIn('document.getElementById("move-btn").onclick = openF6Flow;', app_js) self.assertIn('await apiRequest("GET", "/api/history")', app_js) self.assertIn('Cross-root directory move is not supported in v1', app_js) self.assertIn('Batch directory move is not supported in v1', app_js) self.assertIn('Batch move requires all selected items to be in the same root', app_js) self.assertIn('destination_base', app_js) self.assertIn('sources: selectedItems.map((item) => item.path)', app_js) self.assertIn("function rootKeyFromPath(path)", app_js) self.assertIn("function isNestedPath(sourcePath, destinationPath)", app_js) style_css = (static_root / "style.css").read_text(encoding="utf-8") self.assertIn(':root[data-theme="dark"]', style_css) self.assertIn(':root[data-theme="light"]', style_css) self.assertIn('#theme-toggle', style_css) self.assertIn('.settings-card', style_css) self.assertIn('.settings-tabs', style_css) self.assertIn('.entry-media-slot', style_css) self.assertIn('.entry-media-icon.folder', style_css) self.assertIn('.entry-media-icon.video', style_css) self.assertIn('.entry-media-icon.pdf', style_css) self.assertIn('.entry-media-svg', style_css) self.assertIn('.entry-media-icon.file', style_css) self.assertNotIn('.select-marker', style_css) app_js_url = app.url_path_for("ui", path="/app.js") style_css_url = app.url_path_for("ui", path="/style.css") self.assertEqual(app_js_url, "/ui/app.js") self.assertEqual(style_css_url, "/ui/style.css") if __name__ == "__main__": unittest.main()