krz/orgo

Lightning fast org-mode static site generator.

clone: git clone https://gitbay.org/krz/orgo.git

v0.3.0: tests/site.rs · raw

  1//! Multi-file site build + new-construct snapshots for v0.2 (spec §5, Phase 4/5).
  2//!
  3//! Layers, per the spec's testing philosophy: rendered-HTML snapshots (resolved links,
  4//! templated pages, tables, footnotes) plus explicit assertions that a cross-file link
  5//! resolves to the right URL and that unresolved links are reported, not fatal.
  6
  7use camino::Utf8PathBuf;
  8
  9use org_ssg::index::{SymbolTable, TargetId};
 10use org_ssg::parser::parse;
 11use org_ssg::render::{render, Html, SyntectHighlighter};
 12use org_ssg::resolve::{resolve, ResolvedDoc};
 13use org_ssg::site::{render_site, BuiltPage};
 14
 15fn fixtures() -> Utf8PathBuf {
 16    Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures")
 17}
 18
 19fn build_fixture_site() -> Vec<BuiltPage> {
 20    let (pages, broken) = render_site(&fixtures().join("site")).expect("build site");
 21    assert!(broken.is_empty(), "fixture site has no broken links: {broken:?}");
 22    pages
 23}
 24
 25fn page<'a>(pages: &'a [BuiltPage], source: &str) -> &'a BuiltPage {
 26    pages
 27        .iter()
 28        .find(|p| p.source == source)
 29        .unwrap_or_else(|| panic!("no page for {source}"))
 30}
 31
 32fn render_fragment(name: &str) -> String {
 33    let path = fixtures().join(name);
 34    let source = std::fs::read_to_string(&path).expect("read fixture");
 35    let document = parse(Utf8PathBuf::from(name).as_path(), &source).expect("parse");
 36    let Html(html) = render(&ResolvedDoc { document }, &SyntectHighlighter);
 37    html
 38}
 39
 40#[test]
 41fn site_index_html() {
 42    let pages = build_fixture_site();
 43    insta::assert_snapshot!(page(&pages, "index.org").html);
 44}
 45
 46#[test]
 47fn site_guide_html() {
 48    let pages = build_fixture_site();
 49    insta::assert_snapshot!(page(&pages, "guide.org").html);
 50}
 51
 52/// The invariant the whole RESOLVE stage exists for: a cross-file `[[#setup]]` link on
 53/// the home page must resolve to the guide page's URL plus the target anchor.
 54#[test]
 55fn cross_file_link_resolves() {
 56    let pages = build_fixture_site();
 57    let index = &page(&pages, "index.org").html;
 58    assert!(
 59        index.contains("href=\"guide.html#setup\""),
 60        "cross-file custom-id link should resolve to guide.html#setup, got:\n{index}"
 61    );
 62    // The `file:` link resolves to the bare output path.
 63    assert!(
 64        index.contains("href=\"guide.html\""),
 65        "file: link should resolve to guide.html"
 66    );
 67    // A same-page `[[*Overview]]` link stays a local fragment.
 68    assert!(
 69        index.contains("href=\"#overview\""),
 70        "same-page heading link should be a local fragment"
 71    );
 72}
 73
 74/// Unresolved internal links are reported as warnings, never a crash (spec §4.3.4).
 75#[test]
 76fn unresolved_link_is_reported_not_fatal() {
 77    let source = "Broken [[#does-not-exist][link]] here.\n";
 78    let doc = parse(Utf8PathBuf::from("orphan.org").as_path(), source).expect("parse");
 79    let mut symbols = SymbolTable::new();
 80    symbols.index_document(&doc);
 81    let out = resolve(&doc, &symbols);
 82    assert_eq!(out.broken.len(), 1, "one unresolved link expected");
 83    assert_eq!(
 84        out.broken[0].target,
 85        TargetId::CustomId("does-not-exist".into())
 86    );
 87    assert!(out.used_targets.is_empty(), "nothing resolved, nothing used");
 88}
 89
 90/// RESOLVE records the `uses` edges (spec §4.3, R2) even though incrementality does
 91/// not consume them yet.
 92#[test]
 93fn resolve_records_used_targets() {
 94    let pages = build_fixture_site();
 95    // index.org uses: guide's #setup, guide.org (file), and its own *Overview → 3.
 96    let _ = pages; // pages already assert no broken links; check the edge count directly.
 97    let src = fixtures().join("site").join("index.org");
 98    let source = std::fs::read_to_string(&src).unwrap();
 99    let doc = parse(Utf8PathBuf::from("index.org").as_path(), &source).unwrap();
100
101    let guide_src = fixtures().join("site").join("guide.org");
102    let guide = parse(
103        Utf8PathBuf::from("guide.org").as_path(),
104        &std::fs::read_to_string(&guide_src).unwrap(),
105    )
106    .unwrap();
107
108    let mut symbols = SymbolTable::new();
109    symbols.index_document(&doc);
110    symbols.index_document(&guide);
111    let out = resolve(&doc, &symbols);
112    assert_eq!(out.used_targets.len(), 3, "three internal links resolved");
113    assert!(out.broken.is_empty());
114}
115
116#[test]
117fn table_render() {
118    insta::assert_snapshot!(render_fragment("table.org"));
119}
120
121#[test]
122fn footnote_render() {
123    insta::assert_snapshot!(render_fragment("footnote.org"));
124}