diff --git a/src/render.rs b/src/render.rs index 3b92a0f..56e8040 100644 --- a/src/render.rs +++ b/src/render.rs @@ -393,4 +393,69 @@ mod tests { assert!(html.contains("src=\"logo.png\"")); assert!(!html.contains("title=")); } + + #[test] + fn test_anchor_extraction() { + let md = r#"# Page Title +## Getting Started +Some intro text. +### Installation +Install steps. +## Configuration +Config details. +#### Deep Heading +"#; + let (html, anchors) = markdown_to_html(md); + + // h1 should NOT be extracted (page title, not TOC) + assert!(anchors.iter().all(|a| a.level >= 2)); + + // Should have 4 anchors: h2, h3, h2, h4 + assert_eq!(anchors.len(), 4); + + // Check first anchor + assert_eq!(anchors[0].id, "getting-started"); + assert_eq!(anchors[0].label, "Getting Started"); + assert_eq!(anchors[0].level, 2); + + // Check h3 + assert_eq!(anchors[1].id, "installation"); + assert_eq!(anchors[1].level, 3); + + // Check second h2 + assert_eq!(anchors[2].id, "configuration"); + assert_eq!(anchors[2].level, 2); + + // Check h4 + assert_eq!(anchors[3].id, "deep-heading"); + assert_eq!(anchors[3].level, 4); + + // Verify IDs are in HTML + assert!(html.contains("id=\"getting-started\"")); + assert!(html.contains("id=\"installation\"")); + } + + #[test] + fn test_slugify_edge_cases() { + // Basic case + assert_eq!(slugify("Hello World"), "hello-world"); + + // Multiple spaces → single hyphen + assert_eq!(slugify("Hello World"), "hello-world"); + + // Special characters → hyphen (apostrophe becomes hyphen) + assert_eq!(slugify("What's New?"), "what-s-new"); + + // Numbers preserved, dot becomes hyphen + assert_eq!(slugify("Version 2.0"), "version-2-0"); + + // Leading/trailing spaces trimmed + assert_eq!(slugify(" Padded "), "padded"); + + // Mixed case → lowercase + assert_eq!(slugify("CamelCase"), "camelcase"); + + // Consecutive special chars → single hyphen + assert_eq!(slugify("A -- B"), "a-b"); + } } diff --git a/src/template_engine.rs b/src/template_engine.rs index a08ea02..41b675d 100644 --- a/src/template_engine.rs +++ b/src/template_engine.rs @@ -216,4 +216,96 @@ mod tests { fn test_relative_prefix_depth_2() { assert_eq!(relative_prefix("/blog/posts/foo.html"), "../.."); } + + #[test] + fn test_toc_config_fallback() { + use crate::content::Frontmatter; + + // Create configs with different toc defaults + let config_toc_true = SiteConfig { + title: "Test".to_string(), + author: "Test".to_string(), + base_url: "https://test.com".to_string(), + paths: crate::config::PathsConfig::default(), + nav: crate::config::NavConfig { + nested: false, + toc: true, + }, + }; + + let config_toc_false = SiteConfig { + title: "Test".to_string(), + author: "Test".to_string(), + base_url: "https://test.com".to_string(), + paths: crate::config::PathsConfig::default(), + nav: crate::config::NavConfig { + nested: false, + toc: false, + }, + }; + + // Frontmatter with explicit toc: true + let fm_explicit_true = Frontmatter { + title: "Test".to_string(), + description: None, + date: None, + tags: vec![], + weight: None, + link_to: None, + nav_label: None, + section_type: None, + template: None, + toc: Some(true), + }; + + // Frontmatter with explicit toc: false + let fm_explicit_false = Frontmatter { + title: "Test".to_string(), + description: None, + date: None, + tags: vec![], + weight: None, + link_to: None, + nav_label: None, + section_type: None, + template: None, + toc: Some(false), + }; + + // Frontmatter with no toc specified (None) + let fm_none = Frontmatter { + title: "Test".to_string(), + description: None, + date: None, + tags: vec![], + weight: None, + link_to: None, + nav_label: None, + section_type: None, + template: None, + toc: None, + }; + + // Explicit true overrides config false + let ctx = FrontmatterContext::new(&fm_explicit_true, &config_toc_false); + assert!( + ctx.toc, + "explicit toc: true should override config toc: false" + ); + + // Explicit false overrides config true + let ctx = FrontmatterContext::new(&fm_explicit_false, &config_toc_true); + assert!( + !ctx.toc, + "explicit toc: false should override config toc: true" + ); + + // None falls back to config true + let ctx = FrontmatterContext::new(&fm_none, &config_toc_true); + assert!(ctx.toc, "toc: None should fall back to config toc: true"); + + // None falls back to config false + let ctx = FrontmatterContext::new(&fm_none, &config_toc_false); + assert!(!ctx.toc, "toc: None should fall back to config toc: false"); + } }