use crate::{
Span,
content::{Content, SubstitutionStep},
parser::{ImageRenderParams, InlineSubstitutionRenderer, SpecialCharacter},
tests::{
fixtures::{
image_file_handler::ImageFileHandlerFixture, svg_file_handler::SvgFileHandlerFixture,
},
prelude::*,
},
};
const CIRCLE_SVG: &str = concat!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"500\" height=\"500\" ",
"style=\"fill:red\" viewBox=\"0 0 500 500\">",
"<circle cx=\"250\" cy=\"250\" r=\"200\"/></svg>",
);
const CIRCLE_SVG_BASE64: &str = concat!(
"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0",
"cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIiBzdHls",
"ZT0iZmlsbDpyZWQiIHZpZXdCb3g9IjAgMCA1MDAgNTAwIj48Y2lyY2xlIGN4PSIyNTAiIGN5",
"PSIyNTAiIHI9IjIwMCIvPjwvc3ZnPg==",
);
fn rendered(doc: &crate::Document<'_>) -> String {
rendered_paragraphs(doc).join("\n")
}
#[derive(Debug)]
struct BracketSpecialChars;
impl InlineSubstitutionRenderer for BracketSpecialChars {
fn render_special_character(&self, type_: SpecialCharacter, dest: &mut String) {
match type_ {
SpecialCharacter::Lt => dest.push_str("[LT]"),
SpecialCharacter::Gt => dest.push_str("[GT]"),
SpecialCharacter::Ampersand => dest.push_str("[AMP]"),
}
}
}
#[test]
fn overrides_one_method_and_inherits_the_rest() {
let doc = Parser::default()
.with_inline_substitution_renderer(BracketSpecialChars)
.parse("a < b > c & d *bold*");
assert_eq!(
rendered(&doc),
"a [LT] b [GT] c [AMP] d <strong>bold</strong>"
);
}
#[derive(Debug)]
struct FigureImages;
impl InlineSubstitutionRenderer for FigureImages {
fn render_image(&self, params: &ImageRenderParams, dest: &mut String) {
let uri = self.image_uri(params.target, params.parser, None);
dest.push_str(&format!(
r#"<figure data-src="{uri}">{alt}</figure>"#,
alt = params.alt
));
}
}
#[test]
fn inherited_image_uri_embeds_data_uri_for_a_custom_renderer() {
let doc = Parser::default()
.with_inline_substitution_renderer(FigureImages)
.with_safe_mode(SafeMode::Server)
.with_intrinsic_attribute_bool("data-uri", true, ModificationContext::Anywhere)
.with_intrinsic_attribute("imagesdir", "fixtures", ModificationContext::Anywhere)
.with_image_file_handler(ImageFileHandlerFixture::from_pairs([(
"fixtures/circle.svg",
CIRCLE_SVG.as_bytes(),
)]))
.parse("image:circle.svg[Tiger]");
assert_eq!(
rendered(&doc),
format!(
r#"<figure data-src="data:image/svg+xml;base64,{CIRCLE_SVG_BASE64}">Tiger</figure>"#
)
);
}
#[test]
fn file_handler_accessors_expose_registered_handlers() {
let bare = Parser::default();
assert!(bare.image_file_handler().is_none());
assert!(bare.svg_file_handler().is_none());
let parser = Parser::default()
.with_image_file_handler(ImageFileHandlerFixture::from_pairs([(
"fixtures/circle.svg",
CIRCLE_SVG.as_bytes(),
)]))
.with_svg_file_handler(SvgFileHandlerFixture::from_pairs([(
"fixtures/circle.svg",
CIRCLE_SVG,
)]));
let image_handler = parser
.image_file_handler()
.expect("image file handler should be registered");
assert_eq!(
image_handler.resolve_image("fixtures/circle.svg", &parser),
Some(CIRCLE_SVG.as_bytes().to_vec())
);
let svg_handler = parser
.svg_file_handler()
.expect("SVG file handler should be registered");
assert_eq!(
svg_handler.resolve_svg("fixtures/circle.svg", &parser),
Some(CIRCLE_SVG.to_string())
);
}
#[derive(Debug)]
struct InheritEverything;
impl InlineSubstitutionRenderer for InheritEverything {}
fn assert_inherits_html(
source: &str,
step: SubstitutionStep,
configure: impl Fn(Parser) -> Parser,
) {
let render = |parser: &Parser| {
let mut content = Content::from(Span::new(source));
step.apply(&mut content, parser, None);
content.rendered().to_string()
};
let expected = render(&configure(Parser::default()));
let actual =
render(&configure(Parser::default()).with_inline_substitution_renderer(InheritEverything));
assert_eq!(
actual, expected,
"mismatch rendering {source:?} via {step:?}"
);
}
#[test]
fn an_empty_renderer_matches_the_html_renderer_for_every_substitution() {
let plain = |p: Parser| p;
let experimental = |p: Parser| {
p.with_intrinsic_attribute_bool("experimental", true, ModificationContext::Anywhere)
};
assert_inherits_html("a < b & c > d", SubstitutionStep::SpecialCharacters, plain);
assert_inherits_html(
"(C) (R) (TM) -- ... -> <- => <= it's",
SubstitutionStep::CharacterReplacements,
plain,
);
assert_inherits_html(
"plain *bold* _em_ `code` #marked# [red]#alert#",
SubstitutionStep::Quotes,
plain,
);
assert_inherits_html(
"first line +\nsecond line",
SubstitutionStep::PostReplacement,
plain,
);
assert_inherits_html("code <1>", SubstitutionStep::Callouts, plain);
assert_inherits_html(
"image:foo.png[Alt,200,100]",
SubstitutionStep::Macros,
plain,
);
assert_inherits_html("icon:home[]", SubstitutionStep::Macros, plain);
assert_inherits_html(
"link:https://example.org[text]",
SubstitutionStep::Macros,
plain,
);
assert_inherits_html("[[an-anchor]]", SubstitutionStep::Macros, plain);
assert_inherits_html(
"a ((visible term)) and (((concealed,term)))",
SubstitutionStep::Macros,
plain,
);
assert_inherits_html(
"a footnote:[the note here]",
SubstitutionStep::Macros,
plain,
);
assert_inherits_html("press btn:[OK]", SubstitutionStep::Macros, experimental);
assert_inherits_html("hit kbd:[Ctrl+T]", SubstitutionStep::Macros, experimental);
assert_inherits_html(
"open menu:File[Save > As]",
SubstitutionStep::Macros,
experimental,
);
}
#[test]
fn an_empty_renderer_matches_the_html_renderer_for_cross_references() {
let source = concat!(
"[[the-target]]The target paragraph.\n\n",
"See <<the-target>> and <<missing>>.\n",
);
let default_doc = Parser::default().parse(source);
let inherit_doc = Parser::default()
.with_inline_substitution_renderer(InheritEverything)
.parse(source);
assert_eq!(
rendered_paragraphs(&inherit_doc),
rendered_paragraphs(&default_doc)
);
}