tests/code_cols.rs 5.3 KiB 167 lines Raw
1 //! Code tab stack (f0rge issue #120):2 //! files and README share one stacked `.cols` column at every width, in3 //! DOM order Files then README. No desktop sidebar split.4 //!5 //! Builds a small fixture repo with the real binary and string-asserts on6 //! the emitted HTML + `static/style.css` (no rendering engine needed).7 8 use std::sync::atomic::{AtomicU64, Ordering};9 10 static SEQ: AtomicU64 = AtomicU64::new(0);11 12 use std::path::{Path, PathBuf};13 use std::process::Command;14 15 fn bin() -> &'static str {16     env!("CARGO_BIN_EXE_hu13")17 }18 19 fn manifest() -> PathBuf {20     PathBuf::from(env!("CARGO_MANIFEST_DIR"))21 }22 23 fn fresh(prefix: &str) -> PathBuf {24     let dir = std::env::temp_dir().join(format!(25         "hu13-codecols-{prefix}-{}-{}",26         std::process::id(),27         std::time::SystemTime::now()28             .duration_since(std::time::UNIX_EPOCH)29             .map(|d| d.as_nanos())30             .unwrap_or(0)31             .wrapping_add(u128::from(SEQ.fetch_add(1, Ordering::Relaxed)))32     ));33     std::fs::create_dir_all(&dir).unwrap();34     dir35 }36 37 fn git(repo: &Path, args: &[&str]) {38     let st = Command::new("git")39         .arg("-C")40         .arg(repo)41         .args(args)42         .status()43         .expect("spawn git");44     assert!(st.success(), "git {args:?} failed");45 }46 47 fn fixture_repo() -> PathBuf {48     let dir = fresh("repo");49     git(&dir, &["init", "-b", "main"]);50     git(&dir, &["config", "user.name", "hu13-test"]);51     git(&dir, &["config", "user.email", "[email protected]"]);52     git(&dir, &["config", "commit.gpgsign", "false"]);53     std::fs::write(dir.join("README.md"), "# cols fixture\n\nhello readme\n").unwrap();54     std::fs::write(dir.join("hello.txt"), "hello\n").unwrap();55     git(&dir, &["add", "-A"]);56     git(&dir, &["commit", "-m", "init"]);57     dir58 }59 60 fn build_fixture() -> PathBuf {61     let repo = fixture_repo();62     let out = fresh("out");63     let st = Command::new(bin())64         .arg("build")65         .arg(&repo)66         .arg("-o")67         .arg(&out)68         .status()69         .expect("spawn hu13 build");70     assert!(st.success(), "fixture build exits 0");71     out72 }73 74 fn css() -> String {75     std::fs::read_to_string(manifest().join("static").join("style.css")).expect("read style.css")76 }77 78 #[test]79 fn code_tab_wraps_files_and_readme_in_stacked_column() {80     let out = build_fixture();81     let index = std::fs::read_to_string(out.join("index.html")).expect("index");82     // Wrapper is one plain stacked grid; no code-specific column variant.83     assert!(84         index.contains("<div class=\"cols\">"),85         "code tab wraps files + README in .cols"86     );87     assert!(88         !index.contains("cols-code"),89         "no dead side-by-side class in the markup"90     );91     // Two sections in order: files first, then README.92     let files_pos = index93         .find("<section class=\"code-files\">")94         .expect("files section");95     let readme_pos = index96         .find("<section class=\"code-readme\">")97         .expect("readme section");98     assert!(99         files_pos < readme_pos,100         "files section comes before README section"101     );102     let files_block = &index[files_pos..readme_pos];103     assert!(104         files_block.contains("<h2>Files</h2>"),105         "files section keeps its heading"106     );107     assert!(108         files_block.contains("file-table"),109         "file table lives in the files section"110     );111     let readme_block = &index[readme_pos..];112     assert!(113         readme_block.contains("<h2>README</h2>"),114         "README section keeps its heading"115     );116     assert!(117         readme_block.contains("cols fixture"),118         "README body renders after the file table"119     );120 }121 122 #[test]123 fn code_tab_is_always_stacked() {124     let css = css();125     // Mobile-first base: .cols is a single stacked column.126     let base = css.split("@media (min-width").next().expect("base css");127     assert!(128         base.contains(".cols{display:grid;grid-template-columns:minmax(0,1fr)"),129         "base .cols stays stacked single-column"130     );131     // No code-specific columns and no wide-screen split anywhere.132     assert!(!css.contains(".cols-code"), "no code column variant");133     assert!(134         !css.contains("minmax(0,1.25fr)"),135         "generic .cols no longer goes two-column"136     );137     assert!(138         !css.contains("minmax(0,7fr)") && !css.contains("minmax(0,5fr)"),139         "no desktop 7/5 files+README split"140     );141     assert!(142         css.contains("@media (min-width:1100px)") && css.contains(".wrap{max-width:72rem"),143         "wide layout keeps the readable content width"144     );145 }146 147 #[test]148 fn code_page_uses_one_vertical_rhythm() {149     let css = css();150     // f0rge #122: explicit code-page rhythm, not browser heading margins.151     // 32px between major blocks, 12px heading -> content, 8px bar -> legend.152     for rule in [153         ".cols{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s6)",154         ".repo-head{margin-top:var(--s6);margin-bottom:var(--s6);gap:var(--s3)}",155         ".repo-head h1{margin:0}",156         ".repo-head .clonebox{margin:0}",157         ".code-files>h2,.code-readme>h2{margin:0 0 var(--s3)}",158         ".code-files>.table-scroll{margin:0}",159         ".langs{margin:var(--s6) 0 0}",160         ".langs>h2{margin:0 0 var(--s3)}",161         ".lang-bar{margin:0}",162         ".lang-legend{margin:var(--s2) 0 0}",163         ".code-readme .markdown>:first-child{margin-top:0}",164     ] {165         assert!(css.contains(rule), "code-page rhythm rule missing: {rule}");166     }167 }