use satteri_arena::{Arena, Mdast, StringRef};
use satteri_ast::mdast::MdastNodeType;
use satteri_pulldown_cmark::Options;
use satteri_pulldown_cmark::arena_build::{DEFAULT_OPTIONS, parse};
fn dir_options() -> Options {
DEFAULT_OPTIONS | Options::ENABLE_DIRECTIVE
}
fn node_value(arena: &Arena<Mdast>, id: u32) -> String {
let data = arena.get_type_data(id);
if data.len() >= 8 {
arena.get_str(StringRef::from_bytes(data)).to_string()
} else {
String::new()
}
}
fn types_of(arena: &Arena<Mdast>, parent: u32) -> Vec<u8> {
arena
.get_children(parent)
.iter()
.map(|&id| arena.get_node(id).node_type)
.collect()
}
#[test]
fn closing_fence_after_html_block_does_not_leak() {
let input =
":::note\nParagraph.\n\n<details>\n<summary>See more</summary>\n\nMore.\n\n</details>\n:::";
let (arena, _) = parse(input, dir_options());
let root_children = arena.get_children(0);
assert_eq!(
root_children.len(),
1,
"expected a single container directive"
);
assert_eq!(
arena.get_node(root_children[0]).node_type,
MdastNodeType::ContainerDirective as u8
);
for id in 0..arena.len() as u32 {
let nt = arena.get_node(id).node_type;
if nt == MdastNodeType::Text as u8 || nt == MdastNodeType::Html as u8 {
assert!(
!node_value(&arena, id).contains(":::"),
"closing fence leaked into a node value"
);
}
}
}
#[test]
fn closing_fence_ends_unterminated_pre_block() {
let input = ":::note\n<pre>\ncontent\n:::";
let (arena, _) = parse(input, dir_options());
let root_children = arena.get_children(0);
assert_eq!(root_children.len(), 1);
assert_eq!(
arena.get_node(root_children[0]).node_type,
MdastNodeType::ContainerDirective as u8
);
for id in 0..arena.len() as u32 {
if arena.get_node(id).node_type == MdastNodeType::Html as u8 {
assert!(!node_value(&arena, id).contains(":::"));
}
}
}
#[test]
fn container_directive_label_parses_strong_and_emphasis() {
let input = ":::note[Custom **strong with _emphasis_** Label]\nx\n:::";
let (arena, _) = parse(input, dir_options());
let directive = arena.get_children(0)[0];
let label = arena.get_children(directive)[0];
assert_eq!(
arena.get_node(label).node_type,
MdastNodeType::Paragraph as u8,
"first directive child is the label paragraph"
);
assert_eq!(
types_of(&arena, label),
vec![
MdastNodeType::Text as u8, MdastNodeType::Strong as u8, MdastNodeType::Text as u8, ]
);
let strong = arena.get_children(label)[1];
assert_eq!(
types_of(&arena, strong),
vec![
MdastNodeType::Text as u8, MdastNodeType::Emphasis as u8, ]
);
}
#[test]
fn plain_directive_label_stays_single_text() {
let input = ":::note[Just words]\nx\n:::";
let (arena, _) = parse(input, dir_options());
let directive = arena.get_children(0)[0];
let label = arena.get_children(directive)[0];
assert_eq!(types_of(&arena, label), vec![MdastNodeType::Text as u8]);
}
#[test]
fn leaf_directive_label_is_inline_parsed() {
let input = "::video[A *great* clip]{src=x}";
let (arena, _) = parse(input, dir_options());
let directive = arena.get_children(0)[0];
assert_eq!(
arena.get_node(directive).node_type,
MdastNodeType::LeafDirective as u8
);
assert_eq!(
types_of(&arena, directive),
vec![
MdastNodeType::Text as u8, MdastNodeType::Emphasis as u8, MdastNodeType::Text as u8, ]
);
}
#[test]
fn text_directive_label_is_inline_parsed() {
let input = "see :abbr[be **bold** now] end";
let (arena, _) = parse(input, dir_options());
let para = arena.get_children(0)[0];
let directive = arena.get_children(para)[1];
assert_eq!(
arena.get_node(directive).node_type,
MdastNodeType::TextDirective as u8
);
assert_eq!(
types_of(&arena, directive),
vec![
MdastNodeType::Text as u8, MdastNodeType::Strong as u8, MdastNodeType::Text as u8, ]
);
}
#[test]
fn directive_fragment_inside_code_span_does_not_swallow_later_code() {
let input = "`:foo[` and `bar]`";
let (arena, _) = parse(input, dir_options());
let para = arena.get_children(0)[0];
assert_eq!(
types_of(&arena, para),
vec![
MdastNodeType::InlineCode as u8, MdastNodeType::Text as u8, MdastNodeType::InlineCode as u8, ]
);
let children = arena.get_children(para);
assert_eq!(node_value(&arena, children[0]), ":foo[");
assert_eq!(node_value(&arena, children[2]), "bar]");
}
#[test]
#[should_panic(expected = "unexpected item body DirectiveLabel")]
fn a_container_directive_label_has_no_event() {
satteri_pulldown_cmark::Parser::new_ext(":::a[b]\n:::\n", dir_options()).count();
}
#[test]
fn the_arena_path_builds_that_label() {
let (arena, _) = parse(":::a[b]\n:::\n", dir_options());
let directive = arena.get_children(0)[0];
let label = arena.get_children(directive)[0];
assert_eq!(
arena.get_node(label).node_type,
MdastNodeType::Paragraph as u8
);
assert_eq!(node_value(&arena, arena.get_children(label)[0]), "b");
}
#[test]
fn a_port_directive_survives_an_unlinkable_host() {
let (arena, _) = parse("http://my_app.localhost:3000/admin", dir_options());
let para = arena.get_children(0)[0];
assert_eq!(
types_of(&arena, para),
vec![
MdastNodeType::Text as u8,
MdastNodeType::TextDirective as u8,
MdastNodeType::Text as u8,
]
);
let children = arena.get_children(para);
assert_eq!(node_value(&arena, children[0]), "http://my_app.localhost");
assert_eq!(node_value(&arena, children[1]), "3000");
assert_eq!(node_value(&arena, children[2]), "/admin");
}
#[test]
fn a_linkable_host_still_swallows_the_port() {
let (arena, _) = parse("http://example.com:3000/x", dir_options());
let para = arena.get_children(0)[0];
assert_eq!(types_of(&arena, para), vec![MdastNodeType::Link as u8]);
assert_eq!(
node_value(&arena, arena.get_children(para)[0]),
"http://example.com:3000/x"
);
}