import subprocess
from pathlib import Path
import pytest
from conftest import FS_PROVIDER, Tui, dismiss_launch_popup, make_fs_root
from tui import build
@pytest.fixture
def git_root(tmp_path: Path) -> Path:
root = tmp_path / "code"
for name, files in {
"alpha": {"src/main.rs": "fn render() {}\n"},
"beta": {"notes.txt": "hi\n"},
}.items():
repo = root / name
(repo / "src").mkdir(parents=True, exist_ok=True) if name == "alpha" else repo.mkdir()
for rel, text in files.items():
(repo / rel).write_text(text)
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
subprocess.run(["git", "add", "."], cwd=repo, check=True)
subprocess.run(
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"],
cwd=repo,
check=True,
)
return root
def launch(tmp_path: Path, root: Path, env_extra: dict[str, str] | None = None) -> Tui:
config = tmp_path / "provider.toml"
config.write_text(
f'[provider]\nkind = "stdio"\n'
f'command = ["python3", "{FS_PROVIDER}", "{root}"]\n'
)
return Tui(
build(), cols=110, rows=30, args=["--config", str(config)], env_extra=env_extra
).start()
def test_yank_writes_clipboard(tmp_path: Path) -> None:
clip = tmp_path / "clip.txt"
root = make_fs_root(tmp_path)
tui = launch(tmp_path, root, {"ROOTLE_CLIPBOARD": str(clip)})
try:
tui.type_query("alpha")
tui.key("ENTER")
tui.expect("local/alpha")
tui.key("ENTER")
tui.expect("README.md")
tui.send(" ")
tui.send("y")
screen = tui.expect("yanked")
assert "file://" in screen assert clip.read_text().endswith("/alpha") finally:
tui.stop()
def test_settings_write_back_and_toast(tmp_path: Path) -> None:
root = make_fs_root(tmp_path)
tui = launch(tmp_path, root)
try:
dismiss_launch_popup(tui)
tui.send(":")
tui.type_query("settings")
tui.key("ENTER")
tui.expect("settings")
tui.key("TAB") tui.key("TAB") tui.key("ENTER") for _ in range(3):
tui.key("BACKSPACE")
tui.type_query("128")
tui.key("ENTER") tui.key("ESC") tui.expect("settings saved")
config = Path(tui._home.name) / "config" / "rootle" / "config.toml"
assert "max_mb = 128" in config.read_text()
finally:
tui.stop()
def test_settings_theme_radio_selects_and_saves(tmp_path: Path) -> None:
root = make_fs_root(tmp_path)
xdg = tmp_path / "xdg"
themes = xdg / "rootle" / "themes"
themes.mkdir(parents=True)
(themes / "gruvbox-dark.toml").write_text('[semantic]\nborder_focused = "#b8bb26"\n')
tui = launch(tmp_path, root, env_extra={"XDG_CONFIG_HOME": str(xdg)})
try:
dismiss_launch_popup(tui)
tui.send(":")
tui.type_query("settings")
tui.key("ENTER")
tui.expect("editor")
tui.key("TAB") screen = tui.expect("gruvbox-dark")
assert "\u25cf catppuccin-mocha" in screen
tui.send("j")
tui.send("j")
tui.send(" ") screen = tui.expect("unsaved")
assert "\u25cf gruvbox-dark" in screen
assert "\u25cb catppuccin-mocha" in screen
tui.key("ESC") tui.expect("settings saved")
assert 'name = "gruvbox-dark"' in (xdg / "rootle" / "config.toml").read_text()
finally:
tui.stop()
def test_clone_wizard_runs_git_clone(tmp_path: Path, git_root: Path) -> None:
root = git_root
tui = launch(tmp_path, root)
try:
tui.type_query("zzz") tui.key("ENTER")
tui.expect("local")
tui.key("ENTER")
tui.expect("beta/")
tui.send(":")
tui.type_query("clone")
tui.key("ENTER")
screen = tui.expect("clone — 1/3 repos")
assert "local/alpha" in screen and "local/beta" in screen
tui.send("j")
tui.send(" ")
tui.key("TAB") tui.key("ENTER") tui.expect("2/3 destination")
tui.key("TAB")
tui.key("ENTER") screen = tui.expect("3/3 summary")
assert "git clone" in screen
assert "local/alpha" in screen and "local/beta" not in screen
tui.key("TAB") tui.key("ENTER")
screen = tui.expect("cloned 1 repo")
print(screen)
cloned = Path.cwd() / "local" / "alpha"
assert (cloned / ".git").is_dir(), "clone should materialize a git repo"
finally:
tui.stop()
subprocess.run(["rm", "-rf", str(Path.cwd() / "local")], check=False)
def test_yank_file_yields_blob_url(tmp_path: Path) -> None:
clip = tmp_path / "clip.txt"
root = make_fs_root(tmp_path)
tui = launch(tmp_path, root, {"ROOTLE_CLIPBOARD": str(clip)})
try:
tui.type_query("alpha")
tui.key("ENTER")
tui.expect("local/alpha")
tui.key("ENTER")
tui.expect("main.rs")
tui.send("l") tui.expect("fn render")
tui.send(" ")
tui.send("y")
screen = tui.expect("yanked")
assert "src/main.rs" in screen assert clip.read_text().endswith("/alpha/src/main.rs#L1") finally:
tui.stop()
def test_delete_marked_orgs(tmp_path: Path) -> None:
root = make_fs_root(tmp_path)
tui = launch(tmp_path, root)
try:
tui.type_query("zzz")
tui.key("ENTER")
tui.expect("local")
tui.key("ENTER")
tui.expect("beta/")
tui.send(" ")
tui.send("d")
tui.expect("no marked orgs")
tui.send("h") tui.send("h") tui.send("v")
tui.send(" ") tui.send("v")
tui.send(" ")
tui.send("d")
screen = tui.expect("deleted 1 org")
assert "local/" not in screen finally:
tui.stop()
def test_org_mark_fans_out_to_all_repos(tmp_path: Path) -> None:
root = make_fs_root(tmp_path)
tui = launch(tmp_path, root)
try:
tui.type_query("zzz")
tui.key("ENTER")
tui.expect("local")
tui.key("ENTER")
tui.expect("beta/")
tui.send("h") tui.send("h") tui.send("v")
tui.send(" ") tui.send("v")
tui.send(":")
tui.type_query("clone")
tui.key("ENTER")
screen = tui.expect("clone — 1/3 repos", timeout=8)
assert "local/alpha" in screen and "local/beta" in screen
assert "● local/alpha" in screen finally:
tui.stop()