tests/hide_empty.rs 5.1 KiB 157 lines Raw
1 //! Conditional nav tabs (m6 hide-empty, f0rge issue #38):2 //! `tasks` / `releases` / `wiki` render in the header ONLY when non-empty;3 //! `issues` / `pulls` / `ci` / `code` / `commits` / `agents` always stay.4 //! Hidden sections still build (deep links keep working with their5 //! "No X yet" empty states); only the nav omits the tab.6 7 use std::sync::atomic::{AtomicU64, Ordering};8 9 static SEQ: AtomicU64 = AtomicU64::new(0);10 11 use std::path::{Path, PathBuf};12 use std::process::Command;13 14 fn bin() -> &'static str {15     env!("CARGO_BIN_EXE_hu13")16 }17 18 fn fresh(prefix: &str) -> PathBuf {19     let dir = std::env::temp_dir().join(format!(20         "hu13-hide-empty-{prefix}-{}-{}",21         std::process::id(),22         std::time::SystemTime::now()23             .duration_since(std::time::UNIX_EPOCH)24             .map(|d| d.as_nanos())25             .unwrap_or(0)26             .wrapping_add(u128::from(SEQ.fetch_add(1, Ordering::Relaxed)))27     ));28     std::fs::create_dir_all(&dir).unwrap();29     dir30 }31 32 fn git(repo: &Path, args: &[&str]) {33     let st = Command::new("git")34         .arg("-C")35         .arg(repo)36         .args(args)37         .status()38         .expect("spawn git");39     assert!(st.success(), "git {args:?} failed");40 }41 42 fn forge_write(repo: &Path, rel: &str, content: &str) {43     let dest = repo.join(".forge").join(rel);44     std::fs::create_dir_all(dest.parent().expect("parent")).unwrap();45     std::fs::write(&dest, content).unwrap();46 }47 48 fn bare_repo() -> PathBuf {49     let dir = fresh("repo");50     git(&dir, &["init", "-b", "main"]);51     git(&dir, &["config", "user.name", "hu13-test"]);52     git(&dir, &["config", "user.email", "[email protected]"]);53     git(&dir, &["config", "commit.gpgsign", "false"]);54     std::fs::write(dir.join("README.md"), "# hide-empty fixture\n").unwrap();55     git(&dir, &["add", "-A"]);56     git(&dir, &["commit", "-m", "init"]);57     dir58 }59 60 fn build(repo: &Path) -> PathBuf {61     let out = fresh("out");62     let st = Command::new(bin())63         .arg("build")64         .arg(repo)65         .arg("-o")66         .arg(&out)67         .status()68         .expect("spawn hu13 build");69     assert!(st.success(), "build exits 0");70     out71 }72 73 fn header_of(out: &Path, rel: &str) -> String {74     let html = std::fs::read_to_string(out.join(rel)).expect(rel);75     html.split("</header>")76         .next()77         .expect("header close")78         .to_string()79 }80 81 const ALWAYS_TABS: [&str; 6] = [82     "<a href=\"/\"",83     "<a href=\"/commits.html\"",84     "<a href=\"/issues/index.html\"",85     "<a href=\"/pulls/index.html\"",86     "<a href=\"/ci/index.html\"",87     "<a href=\"/agents.html\"",88 ];89 90 const CONDITIONAL_TABS: [(&str, &str); 3] = [91     ("tasks", "<a href=\"/tasks/index.html\""),92     ("releases", "<a href=\"/releases/index.html\""),93     ("wiki", "<a href=\"/wiki/index.html\""),94 ];95 96 #[test]97 fn empty_sections_hide_tabs_but_pages_still_build() {98     let out = build(&bare_repo());99     // Empty-state pages still build (deep links keep working).100     for (page, marker) in [101         ("tasks/index.html", "No tasks yet"),102         ("releases/index.html", "No releases yet"),103         ("wiki/index.html", "No wiki pages yet"),104     ] {105         let html = std::fs::read_to_string(out.join(page)).expect(page);106         assert!(html.contains(marker), "{page} keeps its empty state");107     }108     // Every sampled header hides the three tabs but keeps the rest.109     for page in ["index.html", "commits.html", "tasks/index.html"] {110         let header = header_of(&out, page);111         for (name, needle) in CONDITIONAL_TABS {112             assert!(!header.contains(needle), "{page} header hides empty {name}");113         }114         for needle in ALWAYS_TABS {115             assert!(116                 header.contains(needle),117                 "{page} header keeps always-shown {needle}"118             );119         }120     }121 }122 123 #[test]124 fn full_forge_shows_all_tabs() {125     let repo = bare_repo();126     forge_write(127         &repo,128         "tasks/77777777-7777-4777-8777-777777777777.md",129         "---\nid: 77777777-7777-4777-8777-777777777777\ntitle: A task\nstatus: todo\ncreated: 2026-09-10T03:00:00Z\n---\nTask body.\n",130     );131     forge_write(132         &repo,133         "releases/v1.0.md",134         "---\nversion: v1.0\ntitle: First\ncreated: 2026-09-10T02:00:00Z\n---\n# v1.0 notes\n",135     );136     forge_write(&repo, "wiki/notes.md", "# Notes\n\nwiki body\n");137     git(&repo, &["add", "-A"]);138     git(&repo, &["commit", "-m", "full forge"]);139     let out = build(&repo);140     let header = header_of(&out, "index.html");141     for (name, needle) in CONDITIONAL_TABS {142         assert!(143             header.contains(needle),144             "populated {name} tab shown in header"145         );146     }147     for needle in ALWAYS_TABS {148         assert!(header.contains(needle), "header keeps {needle}");149     }150     // Populated pages list their content.151     let tasks = std::fs::read_to_string(out.join("tasks/index.html")).expect("tasks");152     assert!(tasks.contains("A task"), "task listed");153     let releases = std::fs::read_to_string(out.join("releases/index.html")).expect("releases");154     assert!(releases.contains("v1.0"), "release listed");155     let wiki = std::fs::read_to_string(out.join("wiki/index.html")).expect("wiki");156     assert!(wiki.contains("Notes"), "wiki page listed");157 }