import subprocess from zotero_kb.api import create_app from zotero_kb.config import AppConfig def _get_index_html(tmp_path) -> str: app = create_app( AppConfig( workspace_dir=tmp_path / "workspace", zotero_data_dir=tmp_path / "zotero", bridge_file=tmp_path / "bridge.json", ) ) for route in app.routes: if getattr(route, "path", None) == "/" and "GET" in getattr(route, "methods", set()): return route.endpoint() raise AssertionError("GET / route not found") def test_index_contains_base_page_forms(tmp_path) -> None: html = _get_index_html(tmp_path) assert 'id="create-project-form"' in html assert 'id="recommend-form"' in html assert 'id="plan-form"' in html def test_index_contains_import_window_controls(tmp_path) -> None: html = _get_index_html(tmp_path) assert 'id="open-import-modal-button"' in html assert 'id="import-window-shell"' in html assert 'id="window-minimize-button"' in html assert 'id="window-maximize-button"' in html assert 'id="window-close-button"' in html assert 'id="window-restore-button"' in html assert 'id="import-window-minimized-bar"' in html assert 'id="import-window-project-label"' in html assert 'id="window-clear-selection-button"' in html assert 'id="window-import-selected-items-button"' in html assert 'id="window-collection-status"' in html assert 'id="window-collection-tree"' in html assert 'id="window-collection-items"' in html assert 'class="window-toolbar-actions"' in html assert 'class="window-action-cluster"' in html assert 'class="window-control-cluster"' in html assert 'isImportWindowOpen: false' in html assert "function openImportWindow()" in html assert "function closeImportWindow()" in html assert "function updateImportActionState()" in html assert 'document.body.classList.toggle("modal-open"' in html def test_index_window_frame_css(tmp_path) -> None: html = _get_index_html(tmp_path) assert ".window-frame" in html assert ".window-shell" in html assert ".window-toolbar" in html assert ".window-body" in html assert ".window-minimized-bar" in html assert '"zotero-kb.import-window"' in html assert "startWindowDrag" in html assert "minimizeWindow" in html assert "maximizeWindow" in html assert "restoreWindow" in html assert "initWindowFromSession" in html assert "persistWindowSessionState" in html def test_index_import_window_has_collection_import_layout(tmp_path) -> None: html = _get_index_html(tmp_path) assert 'class="window-pane collections"' in html assert 'class="window-pane items"' in html assert 'id="window-collection-status"' in html assert 'id="window-collection-tree"' in html assert 'id="window-collection-items"' in html assert ".window-pane.collections" in html assert ".window-pane.items" in html assert ".tree-list" in html assert ".collection-items-list" in html def test_index_has_collection_import_api_calls(tmp_path) -> None: html = _get_index_html(tmp_path) assert "function loadCollectionTree()" in html assert "function loadCollectionItems(collectionKey)" in html assert "/api/zotero/collections/tree" in html assert "/api/zotero/collections/${collectionKey}/items" in html assert "/api/projects/" in html assert "imports/item-keys" in html assert "function importSelectedItems()" in html # openImportWindow calls loadCollectionTree assert "openImportWindow()" in html assert "await loadCollectionTree()" in html def test_index_has_responsive_import_window_geometry_helpers(tmp_path) -> None: html = _get_index_html(tmp_path) assert "function computeResponsiveWindowRect()" in html assert "function applyResponsiveWindowRect()" in html assert "function syncWindowToViewport()" in html assert 'window.addEventListener("resize"' in html def test_index_has_responsive_import_window_layout_rules(tmp_path) -> None: html = _get_index_html(tmp_path) assert "@media (max-width: 980px)" in html assert ".window-pane.items" in html assert ".window-body {" in html assert "grid-template-columns: 1fr;" in html assert "border-bottom: 1px solid var(--line);" in html def test_index_has_project_item_list_and_inline_detail_hooks(tmp_path) -> None: html = _get_index_html(tmp_path) assert 'id="project-item-list"' in html assert "expandedProjectItemKey" in html assert "function renderProjectItems(items)" in html assert "function toggleProjectItemDetail(itemKey)" in html assert ".project-item-detail" in html def test_index_collection_rows_use_readable_unselected_text(tmp_path) -> None: html = _get_index_html(tmp_path) assert ".project-item strong" in html assert ".project-item .meta" in html assert ".collection-row-toggle" in html def test_index_project_item_active_state_uses_high_contrast_text(tmp_path) -> None: html = _get_index_html(tmp_path) assert ".project-item.active {" in html assert "background: var(--accent-strong);" in html assert "color: #f7f2e8;" in html assert ".project-item.active strong," in html assert ".project-item.active .meta {" in html assert "color: inherit;" in html def test_index_has_project_rename_delete_controls(tmp_path) -> None: html = _get_index_html(tmp_path) assert "editingProjectId" in html assert "confirmingDeleteProjectId" in html assert "function startProjectRename(projectId)" in html assert "function cancelProjectRename()" in html assert "function saveProjectRename(projectId)" in html assert "function confirmProjectDelete(projectId)" in html assert "function deleteProject(projectId)" in html assert 'method: "DELETE"' in html assert 'method: "POST"' in html assert 'api(`/api/projects/${projectId}/delete`' in html assert 'error.message === "Method Not Allowed"' in html assert ".project-list-entry" in html assert ".project-list-main" in html assert ".project-list-actions" in html assert ".project-inline-form" in html assert ".project-delete-confirmation" in html def test_index_has_project_item_generate_actions_and_batch_window(tmp_path) -> None: html = _get_index_html(tmp_path) assert 'id="open-batch-generate-button"' in html assert 'id="batch-generate-shell"' in html assert 'id="batch-generate-window"' in html assert 'id="batch-generate-list"' in html assert 'id="select-pending-batch-button"' in html assert 'id="clear-batch-selection-button"' in html assert 'id="start-batch-generate-button"' in html assert 'id="batch-generate-progress"' in html assert "function openBatchGenerateWindow()" in html assert "function closeBatchGenerateWindow()" in html assert "function generateProjectItem(itemKey)" in html assert "function selectAllPendingBatchItems()" in html assert "function runBatchGenerate()" in html assert ".project-item-status-pill" in html assert ".project-item-generate-button" in html def test_index_inline_script_is_valid_javascript(tmp_path) -> None: html = _get_index_html(tmp_path) script_start = html.rfind("") assert script_start != -1 assert script_end != -1 script_path = tmp_path / "index-inline.js" script_path.write_text(html[script_start + len("