use assert_cmd::Command;
use predicates::prelude::*;
fn cli() -> Command {
Command::new(env!("CARGO_BIN_EXE_html-to-markdown"))
}
const TINY_PNG_DATA_URI: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
#[test]
fn should_remove_matching_element_and_descendants_when_single_exclude_selector_is_given() {
cli()
.arg("--exclude-selectors")
.arg(".ad")
.write_stdin(r#"<p>Before</p><div class="ad"><p>Ad content</p></div><p>After</p>"#)
.assert()
.success()
.stdout("Before\n\nAfter\n");
}
#[test]
fn should_remove_all_matching_elements_when_comma_separated_exclude_selectors_are_given() {
cli()
.arg("--exclude-selectors")
.arg(".ad,#sidebar")
.write_stdin(
r#"<p>Before</p><div class="ad"><p>Ad content</p></div><div id="sidebar"><p>Sidebar content</p></div><p>After</p>"#,
)
.assert()
.success()
.stdout("Before\n\nAfter\n");
}
#[test]
fn should_wrap_destination_with_space_in_angle_brackets_by_default() {
cli()
.write_stdin(r#"<p><a href="/a path/page.html">Link</a></p>"#)
.assert()
.success()
.stdout("[Link](</a path/page.html>)\n");
}
#[test]
fn should_percent_encode_destination_with_space_when_url_escape_style_is_percent() {
cli()
.arg("--url-escape-style")
.arg("percent")
.write_stdin(r#"<p><a href="/a path/page.html">Link</a></p>"#)
.assert()
.success()
.stdout("[Link](/a%20path/page.html)\n");
}
#[test]
fn should_reject_invalid_url_escape_style_value() {
cli()
.arg("--url-escape-style")
.arg("bogus")
.write_stdin("<p>Test</p>")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}
#[test]
fn should_include_image_in_json_output_when_max_image_size_is_default() {
let html = format!(r#"<img src="{TINY_PNG_DATA_URI}" alt="Tiny">"#);
cli()
.arg("--json")
.arg("--extract-inline-images")
.write_stdin(html)
.assert()
.success()
.stdout(predicate::str::contains("\"format\": \"png\""));
}
#[test]
fn should_produce_empty_images_array_when_max_image_size_is_smaller_than_decoded_image() {
let html = format!(r#"<img src="{TINY_PNG_DATA_URI}" alt="Tiny">"#);
cli()
.arg("--json")
.arg("--extract-inline-images")
.arg("--max-image-size")
.arg("10")
.write_stdin(html)
.assert()
.success()
.stdout(predicate::str::contains("\"images\": []"));
}
#[test]
fn should_not_capture_inline_svg_when_capture_svg_is_unset() {
let html = r#"<p>text</p><svg width="10" height="10"><rect width="10" height="10"/></svg>"#;
cli()
.arg("--json")
.arg("--extract-inline-images")
.write_stdin(html)
.assert()
.success()
.stdout(predicate::str::contains("\"images\": []"));
}
#[test]
fn should_capture_inline_svg_as_image_when_capture_svg_is_set() {
let html = r#"<p>text</p><svg width="10" height="10"><rect width="10" height="10"/></svg>"#;
cli()
.arg("--json")
.arg("--extract-inline-images")
.arg("--capture-svg")
.write_stdin(html)
.assert()
.success()
.stdout(predicate::str::contains("\"source\": \"svg_element\""));
}
#[test]
fn should_populate_dimensions_by_default_when_extracting_inline_image() {
let html = format!(r#"<img src="{TINY_PNG_DATA_URI}" alt="Tiny">"#);
cli()
.arg("--json")
.arg("--extract-inline-images")
.write_stdin(html)
.assert()
.success()
.stdout(predicate::str::contains(
"\"dimensions\": {\n \"height\": 1,\n \"width\": 1\n }",
));
}
#[test]
fn should_null_dimensions_when_no_infer_dimensions_is_set() {
let html = format!(r#"<img src="{TINY_PNG_DATA_URI}" alt="Tiny">"#);
cli()
.arg("--json")
.arg("--extract-inline-images")
.arg("--no-infer-dimensions")
.write_stdin(html)
.assert()
.success()
.stdout(predicate::str::contains("\"dimensions\": null"));
}
#[test]
fn should_reject_capture_svg_without_extract_inline_images() {
cli()
.arg("--json")
.arg("--capture-svg")
.write_stdin("<p>Test</p>")
.assert()
.failure()
.stderr(predicate::str::contains("required arguments"))
.stderr(predicate::str::contains("--extract-inline-images"));
}
#[test]
fn should_reject_max_image_size_without_extract_inline_images() {
cli()
.arg("--json")
.arg("--max-image-size")
.arg("1024")
.write_stdin("<p>Test</p>")
.assert()
.failure()
.stderr(predicate::str::contains("required arguments"))
.stderr(predicate::str::contains("--extract-inline-images"));
}
#[test]
fn should_reject_no_infer_dimensions_without_extract_inline_images() {
cli()
.arg("--json")
.arg("--no-infer-dimensions")
.write_stdin("<p>Test</p>")
.assert()
.failure()
.stderr(predicate::str::contains("required arguments"))
.stderr(predicate::str::contains("--extract-inline-images"));
}
#[test]
fn should_produce_identical_output_for_tier_strategy_auto_and_tier2() {
let html = "<h1>Title</h1><p>Paragraph with <strong>bold</strong> and <em>italic</em>.</p>\
<ul><li>Item 1</li><li>Item 2</li></ul>\
<table><tr><td>A</td><td>B</td></tr></table>";
let auto_output = cli()
.arg("--tier-strategy")
.arg("auto")
.write_stdin(html)
.assert()
.success()
.get_output()
.stdout
.clone();
let tier2_output = cli()
.arg("--tier-strategy")
.arg("tier2")
.write_stdin(html)
.assert()
.success()
.get_output()
.stdout
.clone();
assert_eq!(
auto_output, tier2_output,
"tier-strategy auto and tier2 must produce identical output for the same input"
);
}
#[test]
fn should_reject_tier1_as_a_tier_strategy_cli_value() {
cli()
.arg("--tier-strategy")
.arg("tier1")
.write_stdin("<p>Test</p>")
.assert()
.failure()
.stderr(predicate::str::contains("invalid value"));
}