use std::path::{Path, PathBuf};
use std::process::Command;
const BIN: &str = env!("CARGO_BIN_EXE_roteiro");
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.args([
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"-c",
"commit.gpgsign=false",
"-c",
"init.defaultBranch=main",
])
.args(args)
.current_dir(dir)
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
fn write(dir: &Path, rel: &str, content: &str) {
let path = dir.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdir");
std::fs::write(path, content).expect("write");
}
fn fresh_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("roteiro-render-cli-{tag}-{}", std::process::id()));
std::fs::remove_dir_all(&dir).ok();
std::fs::create_dir_all(&dir).expect("mkdir");
dir
}
fn source(marker: &str, lines: usize) -> String {
use std::fmt::Write as _;
let mut s = format!("// {marker}: deferred\n");
for i in 1..lines {
let _ = writeln!(s, "pub const N{i}: u32 = {i};");
}
s
}
#[test]
fn render_docs_builds_site_from_adrs_and_assets() {
let dir = fresh_dir("docs");
git(&dir, &["init", "-q"]);
write(&dir, "website/public/style.css", "body{color:#111}\n");
write(&dir, "website/public/index.html", "<h1>Home</h1>\n");
write(&dir, "website/public/favicon.svg", "<svg/>\n");
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n\n## Context\n\n| a | b |\n|---|---|\n| 1 | 2 |\n",
);
write(&dir, "docs/adr/README.md", "index, not an ADR\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let site = dir.join("site");
assert!(site.join("style.css").exists());
assert!(site.join("index.html").exists());
assert!(site.join("favicon.svg").exists());
let page = std::fs::read_to_string(site.join("adr/0001-example.html")).expect("adr page");
assert!(page.starts_with("<!doctype html>"));
assert!(page.contains("<h1>ADR-0001: Example</h1>"));
assert!(page.contains("<table>"), "GFM table should render");
assert!(!page.contains("adr-id"), "frontmatter should be stripped");
assert!(page.contains("← Back to roteiro.dev"));
assert!(
!site.join("adr/README.html").exists(),
"README is not an ADR page"
);
let index = std::fs::read_to_string(site.join("adr/index.html")).expect("index");
assert!(index.contains("<a href=\"0001-example.html\">ADR-0001: Example</a>"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn render_obsidian_home_scopes_debt_by_the_ignore_config() {
let dir = fresh_dir("obsidian");
git(&dir, &["init", "-q"]);
write(&dir, "src/lib.rs", &source("TODO", 100));
write(&dir, "vendor/dep.rs", &source("FIXME", 100));
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let render = |dir: &Path| -> String {
let out = Command::new(BIN)
.args(["render", "obsidian", "--out", "vault"])
.current_dir(dir)
.env("ROTEIRO_HOME", dir)
.output()
.expect("run render");
assert!(out.status.success(), "render obsidian failed: {out:?}");
std::fs::read_to_string(dir.join("vault/_Home.md")).expect("_Home.md")
};
let home = render(&dir);
assert!(
home.contains("| fixme | 1 |") && home.contains("| todo | 1 |"),
"both markers counted with no ignore config: {home}"
);
assert!(
home.contains("vendor/dep.rs") && home.contains("src/lib.rs"),
"both files ranked with no ignore config: {home}"
);
write(&dir, "roteiro.toml", "[debt]\nignore = [\"vendor/**\"]\n");
let home = render(&dir);
assert!(
!home.contains("fixme"),
"ignored marker must not reach the category totals: {home}"
);
assert!(
home.contains("| todo | 1 |"),
"the marker still in scope is still counted: {home}"
);
assert!(
!home.contains("vendor"),
"ignored file must not reach the density table: {home}"
);
assert!(
home.contains("src/lib.rs"),
"the file still in scope is still ranked: {home}"
);
std::fs::remove_dir_all(&dir).ok();
}