use super::*;
use crate::config::Marmite;
use crate::content::{Content, GroupedContent, Kind};
use crate::site::Data;
use std::collections::HashMap;
pub fn get_html(markdown: &str) -> String {
get_html_with_options(markdown, &ParserOptions::default(), None)
}
#[test]
fn test_fix_internal_links_with_md_extension() {
let html = r#"<a href="test.md">test.md</a>"#;
let expected = r#"<a href="test.html">test</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_with_html_extension() {
let html = r#"<a href="test.html">test.html</a>"#;
let expected = r#"<a href="test.html">test</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_without_extension() {
let html = r#"<a href="test">test</a>"#;
let expected = r#"<a href="test.html">test</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_external_link() {
let html = r#"<a href="http://example.com">example</a>"#;
let expected = r#"<a href="http://example.com">example</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_mixed_content() {
let html = r#"<a href="test.md">test.md</a> and <a href="http://example.com">example</a>"#;
let expected = r#"<a href="test.html">test</a> and <a href="http://example.com">example</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_get_links_to_with_internal_links() {
let html = r#"<a href="./test1.html">test1</a> <a href="./test2.html">test2</a>"#;
let expected = Some(vec!["test1".to_string(), "test2".to_string()]);
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_links_to_with_internal_links_no_slash() {
let html = r#"<a href="test1.html">test1</a> <a href="test2.html">test2</a>"#;
let expected = Some(vec!["test1".to_string(), "test2".to_string()]);
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_links_to_with_no_internal_links() {
let html = r#"<a href="http://example.com">example</a>"#;
let expected: Option<Vec<String>> = None;
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_links_to_with_mixed_links() {
let html = r#"<a href="./test1.html">test1</a> <a href="test2.html">test2</a> <a href="http://example.com">example</a>"#;
let expected = Some(vec!["test1".to_string(), "test2".to_string()]);
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_links_to_with_no_links() {
let html = r"<p>No links here</p>";
let expected: Option<Vec<String>> = None;
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_links_to_with_empty_string() {
let html = "";
let expected: Option<Vec<String>> = None;
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_links_to_with_internal_link_with_heading() {
let html = r#"<a href="test1.html#heading">test</a>"#;
let expected = Some(vec!["test1#heading".to_string()]);
assert_eq!(get_links_to(html), expected);
}
#[test]
fn test_get_media_links_to_with_images() {
let html =
r#"<img src="media/photo.jpg" alt="photo"><img src="media/banner.png" alt="banner">"#;
let result = get_media_links_to(html).unwrap();
assert_eq!(result.len(), 2);
assert!(result.contains(&"media/photo.jpg".to_string()));
assert!(result.contains(&"media/banner.png".to_string()));
}
#[test]
fn test_get_media_links_to_with_href() {
let html = r#"<a href="media/report.pdf">Download</a>"#;
let result = get_media_links_to(html).unwrap();
assert_eq!(result, vec!["media/report.pdf".to_string()]);
}
#[test]
fn test_get_media_links_to_skips_external() {
let html = r#"<img src="https://example.com/photo.jpg" alt="photo">"#;
assert_eq!(get_media_links_to(html), None);
}
#[test]
fn test_get_media_links_to_skips_html_links() {
let html = r#"<a href="about.html">About</a>"#;
assert_eq!(get_media_links_to(html), None);
}
#[test]
fn test_get_media_links_to_empty() {
assert_eq!(get_media_links_to(""), None);
assert_eq!(get_media_links_to("<p>No links here</p>"), None);
}
#[test]
fn test_get_media_links_to_normalizes_dot_slash() {
let html = r#"<img src="./media/photo.jpg" alt="photo">"#;
let result = get_media_links_to(html).unwrap();
assert_eq!(result, vec!["media/photo.jpg".to_string()]);
}
#[test]
fn test_get_media_links_to_deduplicates() {
let html = r#"<img src="media/photo.jpg"><a href="media/photo.jpg">link</a>"#;
let result = get_media_links_to(html).unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_get_media_links_to_subfolder_media() {
let html = r#"<img src="media/my-post/diagram.svg" alt="diagram">"#;
let result = get_media_links_to(html).unwrap();
assert_eq!(result, vec!["media/my-post/diagram.svg".to_string()]);
}
#[test]
fn test_get_html_basic_markdown() {
let markdown = "# Title\n\nThis is a paragraph.";
let expected = "<h1 id=\"title\">Title<a href=\"#title\" aria-label=\"Link to heading 'Title'\" data-heading-content=\"Title\" class=\"anchor\"></a></h1>\n<p>This is a paragraph.</p>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_html_with_links() {
let markdown = "[example](http://example.com)";
let expected = "<p><a href=\"http://example.com\">example</a></p>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_html_with_internal_relative_links() {
let markdown = "[internal](./test.md)";
let expected = "<p><a href=\"./test.md\">internal</a></p>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_html_with_internal_links_no_slash() {
let markdown = "[internal](test.md)";
let expected = "<p><a href=\"test.html\">internal</a></p>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_html_with_images() {
let markdown = "";
let expected = "<p><figure><img src=\"media/image.jpg\" alt=\"alt text\" /></figure></p>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_blockquotes_without_space() {
let markdown = ">testing blockquote";
let expected = "<blockquote>\n<p>testing blockquote</p>\n</blockquote>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_blockquotes_with_space() {
let markdown = "> testing blockquote";
let expected = "<blockquote>\n<p>testing blockquote</p>\n</blockquote>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_blockquotes_with_multiline() {
let markdown = "> testing blockquote\n> line 2";
let expected = "<blockquote>\n<p>testing blockquote\nline 2</p>\n</blockquote>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_blockquotes_with_triple() {
let markdown = ">>>\ntesting blockquote\n line 2\n>>>";
let expected = "<blockquote>\n<p>testing blockquote\nline 2</p>\n</blockquote>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_fix_internal_links_with_media_files() {
let html = r#"<a href="media/image.jpg">View Image</a>"#;
let expected = r#"<a href="media/image.jpg">View Image</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_with_media_files_webp() {
let html = r#"<a href="/media/homecloudpart1-mikrotik.webp">View Image</a>"#;
let expected = r#"<a href="/media/homecloudpart1-mikrotik.webp">View Image</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_with_various_media_extensions() {
let test_cases = vec![
("media/image.jpg", "media/image.jpg"),
("media/image.jpeg", "media/image.jpeg"),
("media/image.png", "media/image.png"),
("media/image.gif", "media/image.gif"),
("media/image.webp", "media/image.webp"),
("media/image.svg", "media/image.svg"),
("media/image.avif", "media/image.avif"),
("media/video.mp4", "media/video.mp4"),
("media/audio.mp3", "media/audio.mp3"),
("media/document.pdf", "media/document.pdf"),
];
for (input, expected) in test_cases {
let html = format!(r#"<a href="{input}">Link</a>"#);
let expected_html = format!(r#"<a href="{expected}">Link</a>"#);
assert_eq!(fix_internal_links(&html), expected_html);
}
}
#[test]
fn test_get_html_with_code_block() {
let markdown = "```\nlet x = 1;\n```";
let expected = "<pre><code>let x = 1;\n</code></pre>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_html_with_task_list() {
let markdown = "- [x] Task 1\n- [ ] Task 2";
let expected = "<ul>\n<li><input type=\"checkbox\" checked=\"\" disabled=\"\" /> Task 1</li>\n<li><input type=\"checkbox\" disabled=\"\" /> Task 2</li>\n</ul>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_html_with_table() {
let markdown = "| Header1 | Header2 |\n| ------- | ------- |\n| Cell1 | Cell2 |";
let expected = "<table>\n<thead>\n<tr>\n<th>Header1</th>\n<th>Header2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Cell1</td>\n<td>Cell2</td>\n</tr>\n</tbody>\n</table>\n";
assert_eq!(get_html(markdown), expected);
}
#[test]
fn test_get_table_of_contents_from_html_with_single_header() {
let html = r##"<h1 id="header-1">Header 1<a href="#header-1" aria-label="Link to heading 'Header 1'" data-heading-content="Header 1" class="anchor"></a></h1>"##;
let expected = "<ul>\n<li><a href=\"#header-1\">Header 1</a></li>\n</ul>\n";
assert_eq!(get_table_of_contents_from_html(html), expected);
}
#[test]
fn test_get_table_of_contents_from_html_with_multiple_headers() {
let html = r##"
<h1 id="header-1">Header 1<a href="#header-1" class="anchor"></a></h1>
<h2 id="header-2">Header 2<a href="#header-2" class="anchor"></a></h2>
<h3 id="header-3">Header 3<a href="#header-3" class="anchor"></a></h3>
"##;
let expected = "<ul>\n<li><a href=\"#header-1\">Header 1</a></li>\n<ul>\n<li><a href=\"#header-2\">Header 2</a></li>\n<ul>\n<li><a href=\"#header-3\">Header 3</a></li>\n</ul>\n</ul>\n</ul>\n";
assert_eq!(get_table_of_contents_from_html(html), expected);
}
#[test]
fn test_get_table_of_contents_from_html_with_nested_headers() {
let html = r##"
<h1 id="header-1">Header 1<a href="#header-1" class="anchor"></a></h1>
<h2 id="header-2">Header 2<a href="#header-2" class="anchor"></a></h2>
<h1 id="header-3">Header 3<a href="#header-3" class="anchor"></a></h1>
"##;
let expected = "<ul>\n<li><a href=\"#header-1\">Header 1</a></li>\n<ul>\n<li><a href=\"#header-2\">Header 2</a></li>\n</ul>\n<li><a href=\"#header-3\">Header 3</a></li>\n</ul>\n";
assert_eq!(get_table_of_contents_from_html(html), expected);
}
#[test]
fn test_get_table_of_contents_from_html_with_no_headers() {
let html = r"<p>No headers here</p>";
let expected = "";
assert_eq!(get_table_of_contents_from_html(html), expected);
}
#[test]
fn test_get_table_of_contents_from_html_with_mixed_content() {
let html = r##"
<h1 id="header-1">Header 1<a href="#header-1" class="anchor"></a></h1>
<p>Some content</p>
<h2 id="header-2">Header 2<a href="#header-2" class="anchor"></a></h2>
<p>More content</p>
"##;
let expected = "<ul>\n<li><a href=\"#header-1\">Header 1</a></li>\n<ul>\n<li><a href=\"#header-2\">Header 2</a></li>\n</ul>\n</ul>\n";
assert_eq!(get_table_of_contents_from_html(html), expected);
}
#[test]
fn test_get_table_of_contents_from_html_without_id_falls_back_to_slugify() {
let html = r#"<h1>My Heading</h1>"#;
let expected = "<ul>\n<li><a href=\"#my-heading\">My Heading</a></li>\n</ul>\n";
assert_eq!(get_table_of_contents_from_html(html), expected);
}
#[test]
fn test_get_table_of_contents_from_comrak_output() {
let markdown =
"# How it works\n\nSome text\n\n## Getting Started\n\nMore text\n\n## Advanced Usage\n";
let html = get_html(markdown);
let toc = get_table_of_contents_from_html(&html);
assert!(
toc.contains(r##"href="#how-it-works""##),
"TOC should link to #how-it-works, got: {toc}"
);
assert!(
toc.contains(r##"href="#getting-started""##),
"TOC should link to #getting-started, got: {toc}"
);
assert!(
toc.contains(r##"href="#advanced-usage""##),
"TOC should link to #advanced-usage, got: {toc}"
);
assert!(
toc.contains(">How it works<"),
"TOC should show clean title text, got: {toc}"
);
assert!(
toc.contains(">Getting Started<"),
"TOC should show clean title text, got: {toc}"
);
assert!(
!toc.contains("anchor"),
"TOC should not contain anchor class, got: {toc}"
);
assert!(
!toc.contains("aria-label"),
"TOC should not contain aria-label, got: {toc}"
);
}
fn create_test_site_data() -> Data {
let posts = vec![
Content {
title: "TWSBI Eco Indigo Blue & de Atramentis Document Brown".to_string(),
slug: "twsbi-eco-indigo-blue-de-atramentis-document-brown".to_string(),
..Default::default()
},
Content {
title: "Another Post".to_string(),
slug: "another-post".to_string(),
..Default::default()
},
];
let pages = vec![Content {
title: "About Page".to_string(),
slug: "about".to_string(),
..Default::default()
}];
Data {
site: Marmite::default(),
posts,
pages,
tag: GroupedContent::new(Kind::Tag),
archive: GroupedContent::new(Kind::Archive),
author: GroupedContent::new(Kind::Author),
stream: GroupedContent::new(Kind::Stream),
series: GroupedContent::new(Kind::Series),
language: GroupedContent::new(Kind::Language),
latest_timestamp: None,
config_path: String::new(),
force_render: false,
generated_urls: crate::site::UrlCollection::default(),
galleries: HashMap::new(),
}
}
#[test]
fn test_fix_wikilinks_exact_title_match() {
let site_data = create_test_site_data();
let html = r#"<a href="twsbi-eco-indigo-blue-amp-de-atramentis-document-brown.html" data-wikilink="true">TWSBI Eco Indigo Blue & de Atramentis Document Brown</a>"#;
let expected = r#"<a href="twsbi-eco-indigo-blue-de-atramentis-document-brown.html" data-wikilink="true">TWSBI Eco Indigo Blue & de Atramentis Document Brown</a>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_wikilinks_case_insensitive() {
let site_data = create_test_site_data();
let html = r#"<a href="another-post-wrong.html" data-wikilink="true">another post</a>"#;
let expected = r#"<a href="another-post.html" data-wikilink="true">another post</a>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_wikilinks_no_match_unchanged() {
let site_data = create_test_site_data();
let html = r#"<a href="nonexistent-post.html" data-wikilink="true">Nonexistent Post</a>"#;
let expected = r#"<a href="nonexistent-post.html" data-wikilink="true">Nonexistent Post</a>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_wikilinks_multiple_links() {
let site_data = create_test_site_data();
let html = r#"<p>Check out <a href="another-post-wrong.html" data-wikilink="true">Another Post</a> and <a href="about-wrong.html" data-wikilink="true">About Page</a>.</p>"#;
let expected = r#"<p>Check out <a href="another-post.html" data-wikilink="true">Another Post</a> and <a href="about.html" data-wikilink="true">About Page</a>.</p>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_wikilinks_html_entities() {
let site_data = create_test_site_data();
let html = r#"<a href="twsbi-wrong.html" data-wikilink="true">TWSBI Eco Indigo Blue & de Atramentis Document Brown</a>"#;
let expected = r#"<a href="twsbi-eco-indigo-blue-de-atramentis-document-brown.html" data-wikilink="true">TWSBI Eco Indigo Blue & de Atramentis Document Brown</a>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_wikilinks_ignores_regular_links() {
let site_data = create_test_site_data();
let html = r#"<a href="another-post-wrong.html">Another Post</a> and <a href="external.com">External</a>"#;
let expected = r#"<a href="another-post-wrong.html">Another Post</a> and <a href="external.com">External</a>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_wikilinks_mixed_content() {
let site_data = create_test_site_data();
let html = r#"<p>Regular <a href="external.com">external link</a> and <a href="wrong.html" data-wikilink="true">About Page</a> wikilink.</p>"#;
let expected = r#"<p>Regular <a href="external.com">external link</a> and <a href="about.html" data-wikilink="true">About Page</a> wikilink.</p>"#;
assert_eq!(fix_wikilinks(html, &site_data), expected);
}
#[test]
fn test_fix_internal_links_with_percent_encoded_spaces() {
let html = r#"<a href="Markdown%20Format" data-wikilink="true">Markdown Format</a>"#;
let expected = r#"<a href="markdown-format.html" data-wikilink="true">Markdown Format</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_fix_internal_links_with_percent_encoded_spaces_and_fragment() {
let html =
r#"<a href="Markdown%20Format#Obsidian%20Links" data-wikilink="true">Markdown Format</a>"#;
let expected =
r#"<a href="markdown-format.html#obsidian-links" data-wikilink="true">Markdown Format</a>"#;
assert_eq!(fix_internal_links(html), expected);
}
#[test]
fn test_decode_html_entities() {
assert_eq!(decode_html_entities("Test & Example"), "Test & Example");
assert_eq!(decode_html_entities("<tag>"), "<tag>");
assert_eq!(decode_html_entities(""quoted""), "\"quoted\"");
assert_eq!(decode_html_entities("It's working"), "It's working");
assert_eq!(
decode_html_entities("Mixed & 'entities' <here>"),
"Mixed & 'entities' <here>"
);
}
#[test]
fn test_render_native_mermaid_basic() {
let html = r#"<p>Before</p><pre class="marmite-code"><code class="marmite-code-inner language-mermaid">graph LR
A --> B</code></pre><p>After</p>"#;
let result = render_native_mermaid(html, "test-slug", None);
assert!(result.contains("<div class=\"mermaid-diagram\">"));
assert!(result.contains("<svg"));
assert!(result.contains("<p>Before</p>"));
assert!(result.contains("<p>After</p>"));
assert!(!result.contains("language-mermaid"));
}
#[test]
fn test_render_native_mermaid_preserves_non_mermaid_blocks() {
let html = r#"<pre class="marmite-code"><code class="marmite-code-inner language-rust">fn main() {}</code></pre>"#;
let result = render_native_mermaid(html, "test-slug", None);
assert_eq!(result, html);
}
#[test]
fn test_render_native_mermaid_no_mermaid_blocks() {
let html = "<p>No code blocks here</p>";
let result = render_native_mermaid(html, "test-slug", None);
assert_eq!(result, html);
}
#[test]
fn test_render_native_mermaid_with_dark_theme() {
let config: serde_yaml::Value = serde_yaml::from_str("theme: dark").unwrap();
let html = r#"<pre class="marmite-code"><code class="marmite-code-inner language-mermaid">graph LR
A --> B</code></pre>"#;
let result = render_native_mermaid(html, "test-slug", Some(&config));
assert!(result.contains("<div class=\"mermaid-diagram\">"));
assert!(result.contains("<svg"));
}
#[test]
fn test_render_native_mermaid_with_spacing_config() {
let config: serde_yaml::Value = serde_yaml::from_str(
r#"
flowchart:
nodeSpacing: 80
rankSpacing: 60
"#,
)
.unwrap();
let html = r#"<pre class="marmite-code"><code class="marmite-code-inner language-mermaid">graph LR
A --> B</code></pre>"#;
let result = render_native_mermaid(html, "test-slug", Some(&config));
assert!(result.contains("<div class=\"mermaid-diagram\">"));
assert!(result.contains("<svg"));
}
#[test]
fn test_render_native_mermaid_invalid_config_falls_back() {
let config: serde_yaml::Value = serde_yaml::from_str("theme: nonexistent_theme").unwrap();
let html = r#"<pre class="marmite-code"><code class="marmite-code-inner language-mermaid">graph LR
A --> B</code></pre>"#;
let result = render_native_mermaid(html, "test-slug", Some(&config));
assert!(result.contains("<svg") || result.contains("language-mermaid"));
}