Clide is being rebuilt as a Flutter desktop app. The Python Textual implementation moves wholesale into legacy/ rather than being deleted: its pane model, panel set, git skills, and panel communication design are real thought that should remain readable next to the new code while the rebuild finds its shape. Git's rename tracking preserves history, so `git log -- legacy/` still works. The Flutter rebuild lives at the repo root alongside a Go sidecar (the architecture claudian was heading toward, which folds into clide as a core component rather than a separate plugin project). Bootstrap of the new shape lands in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
151 lines
5.1 KiB
Python
151 lines
5.1 KiB
Python
"""Integration tests for FilesView widget."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from textual.app import App, ComposeResult
|
|
|
|
from clide.widgets.components.files_view import FilesView
|
|
|
|
|
|
class FilesViewTestApp(App):
|
|
"""Test app for FilesView."""
|
|
|
|
def __init__(self, path: Path) -> None:
|
|
super().__init__()
|
|
self.test_path = path
|
|
self.selected_files: list[Path] = []
|
|
self.selected_dirs: list[Path] = []
|
|
|
|
def compose(self) -> ComposeResult:
|
|
yield FilesView(self.test_path, id="files")
|
|
|
|
def on_files_view_file_selected(self, event: FilesView.FileSelected) -> None:
|
|
self.selected_files.append(event.path)
|
|
|
|
def on_files_view_directory_selected(self, event: FilesView.DirectorySelected) -> None:
|
|
self.selected_dirs.append(event.path)
|
|
|
|
|
|
@pytest.fixture
|
|
def test_directory(tmp_path: Path) -> Path:
|
|
"""Create a test directory structure."""
|
|
# Create directories
|
|
(tmp_path / "src").mkdir()
|
|
(tmp_path / "src" / "components").mkdir()
|
|
(tmp_path / "tests").mkdir()
|
|
|
|
# Create files
|
|
(tmp_path / "README.md").write_text("# Test")
|
|
(tmp_path / "src" / "main.py").write_text("print('hello')")
|
|
(tmp_path / "src" / "components" / "button.py").write_text("class Button: pass")
|
|
(tmp_path / "tests" / "test_main.py").write_text("def test_main(): pass")
|
|
|
|
return tmp_path
|
|
|
|
|
|
async def test_files_view_renders(test_directory: Path):
|
|
"""Test that FilesView renders without errors."""
|
|
app = FilesViewTestApp(test_directory)
|
|
async with app.run_test():
|
|
files_view = app.query_one("#files", FilesView)
|
|
assert files_view is not None
|
|
assert files_view.path == test_directory
|
|
|
|
|
|
async def test_files_view_shows_files(test_directory: Path):
|
|
"""Test that FilesView shows files in the directory."""
|
|
app = FilesViewTestApp(test_directory)
|
|
async with app.run_test():
|
|
files_view = app.query_one("#files", FilesView)
|
|
# The root should be loaded
|
|
assert files_view.root is not None
|
|
|
|
|
|
async def test_directory_click_expands(test_directory: Path):
|
|
"""Test that clicking a directory expands it and emits event."""
|
|
app = FilesViewTestApp(test_directory)
|
|
async with app.run_test() as pilot:
|
|
files_view = app.query_one("#files", FilesView)
|
|
|
|
# Wait for initial load
|
|
await pilot.pause()
|
|
|
|
# Find the src directory node and click it
|
|
for node in files_view.root.children:
|
|
if node.data and node.data.path.name == "src":
|
|
files_view.select_node(node)
|
|
await pilot.pause()
|
|
break
|
|
|
|
# Check that directory was selected
|
|
assert len(app.selected_dirs) >= 1
|
|
assert any(p.name == "src" for p in app.selected_dirs)
|
|
|
|
|
|
async def test_file_click_emits_event(test_directory: Path):
|
|
"""Test that clicking a file emits FileSelected event."""
|
|
app = FilesViewTestApp(test_directory)
|
|
async with app.run_test() as pilot:
|
|
files_view = app.query_one("#files", FilesView)
|
|
|
|
# Wait for initial load
|
|
await pilot.pause()
|
|
|
|
# Find and click README.md
|
|
for node in files_view.root.children:
|
|
if node.data and node.data.path.name == "README.md":
|
|
files_view.select_node(node)
|
|
await pilot.pause()
|
|
break
|
|
|
|
# Check that file was selected
|
|
assert len(app.selected_files) >= 1
|
|
assert any(p.name == "README.md" for p in app.selected_files)
|
|
|
|
|
|
async def test_filter_paths_hides_noisy_dirs(test_directory: Path):
|
|
"""Test that noisy directories are filtered but hidden files are shown."""
|
|
# Create hidden files/dirs and noisy dirs
|
|
(test_directory / ".git").mkdir()
|
|
(test_directory / ".hidden_file").write_text("hidden")
|
|
(test_directory / "__pycache__").mkdir()
|
|
|
|
app = FilesViewTestApp(test_directory)
|
|
async with app.run_test() as pilot:
|
|
files_view = app.query_one("#files", FilesView)
|
|
|
|
# Wait for initial load
|
|
await pilot.pause()
|
|
|
|
# Get visible items in the tree
|
|
visible_names = {node.data.path.name for node in files_view.root.children if node.data}
|
|
|
|
# Noisy directories should be filtered out
|
|
assert ".git" not in visible_names
|
|
assert "__pycache__" not in visible_names
|
|
|
|
# Hidden files are shown (they'll be dimmed in the UI)
|
|
assert ".hidden_file" in visible_names
|
|
|
|
# Regular files/dirs are shown
|
|
assert "src" in visible_names
|
|
assert "README.md" in visible_names
|
|
|
|
|
|
async def test_render_label_shows_icons(test_directory: Path):
|
|
"""Test that render_label produces proper icons."""
|
|
app = FilesViewTestApp(test_directory)
|
|
async with app.run_test() as pilot:
|
|
files_view = app.query_one("#files", FilesView)
|
|
|
|
# Wait for initial load
|
|
await pilot.pause()
|
|
|
|
# Check that nodes have labels rendered (icons are part of label)
|
|
for node in files_view.root.children:
|
|
if node.data:
|
|
# The label should contain the filename
|
|
label_text = str(node.label)
|
|
assert node.data.path.name in label_text
|