use super::*;
use crate::content_graph::{ContentGraph, ContentGraphBuilder};
#[test]
fn pothole_empty_string_is_empty() {
assert_eq!(parse_pothole_params(""), PotholeContent::Empty);
assert_eq!(parse_pothole_params(" "), PotholeContent::Empty);
}
#[test]
fn pothole_pure_digit_is_alias_not_width_token() {
match parse_pothole_params("400") {
PotholeContent::Alias(s) => assert_eq!(s, "400"),
other => panic!("expected Alias, got {:?}", other),
}
}
#[test]
fn pothole_plain_alias() {
match parse_pothole_params("My alias") {
PotholeContent::Alias(s) => assert_eq!(s, "My alias"),
other => panic!("expected Alias, got {:?}", other),
}
}
#[test]
fn pothole_kv_pair_is_params() {
match parse_pothole_params("width=400 align=left") {
PotholeContent::Params(p) => {
assert_eq!(p.get("width"), Some("400"));
assert_eq!(p.get("align"), Some("left"));
}
other => panic!("expected Params, got {:?}", other),
}
}
#[test]
fn pothole_single_kv_is_params() {
match parse_pothole_params("width=400") {
PotholeContent::Params(p) => {
assert_eq!(p.get("width"), Some("400"));
}
other => panic!("expected Params, got {:?}", other),
}
}
#[test]
fn pothole_bare_alt_blocks_kv_parse() {
match parse_pothole_params("alt text=cover") {
PotholeContent::Alias(s) => assert_eq!(s, "alt text=cover"),
other => panic!("expected Alias, got {:?}", other),
}
}
#[test]
fn pothole_uppercase_key_blocks_kv_parse() {
match parse_pothole_params("My Notes=Important") {
PotholeContent::Alias(s) => assert_eq!(s, "My Notes=Important"),
other => panic!("expected Alias, got {:?}", other),
}
}
#[test]
fn pothole_no_equals_is_alias() {
match parse_pothole_params("width 400") {
PotholeContent::Alias(s) => assert_eq!(s, "width 400"),
other => panic!("expected Alias, got {:?}", other),
}
}
#[test]
fn pothole_partial_kv_falls_through_to_alias() {
match parse_pothole_params("width=400 caption text") {
PotholeContent::Alias(s) => assert_eq!(s, "width=400 caption text"),
other => panic!("expected Alias, got {:?}", other),
}
}
#[test]
fn pothole_kv_with_hyphenated_key() {
match parse_pothole_params("aria-label=primary data_id=42") {
PotholeContent::Params(p) => {
assert_eq!(p.get("aria-label"), Some("primary"));
assert_eq!(p.get("data_id"), Some("42"));
}
other => panic!("expected Params, got {:?}", other),
}
}
#[test]
fn pothole_obsidian_width_keyword() {
match parse_pothole_params("wide") {
PotholeContent::WidthToken { width, rest_alias } => {
assert_eq!(width, "wide");
assert!(rest_alias.is_empty());
}
other => panic!("expected WidthToken, got {:?}", other),
}
}
#[test]
fn split_dest_url_plain_file() {
let s = split_dest_url("notes");
assert_eq!(s.file, "notes");
assert_eq!(s.section, None);
assert_eq!(s.query, None);
}
#[test]
fn split_dest_url_with_anchor() {
let s = split_dest_url("notes#section");
assert_eq!(s.file, "notes");
assert_eq!(s.section, Some("section"));
assert_eq!(s.query, None);
}
#[test]
fn split_dest_url_with_query() {
let s = split_dest_url("page.html?x=1");
assert_eq!(s.file, "page.html");
assert_eq!(s.query, Some("x=1"));
}
#[test]
fn split_dest_url_anchor_then_query() {
let s = split_dest_url("page.html#frag?x=1");
assert_eq!(s.file, "page.html");
assert_eq!(s.section, Some("frag"));
assert_eq!(s.query, Some("x=1"));
}
#[test]
fn split_dest_url_query_then_anchor() {
let s = split_dest_url("page.html?x=1#frag");
assert_eq!(s.file, "page.html");
assert_eq!(s.query, Some("x=1"));
assert_eq!(s.section, Some("frag"));
}
fn build_graph(paths: &[&str]) -> ContentGraph {
let mut b = ContentGraphBuilder::new();
for p in paths {
let slug = std::path::Path::new(p)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(p);
b.add_file(p, slug);
}
b.build()
}
fn empty_snapshot() -> AssetSnapshot {
AssetSnapshot::new()
}
#[test]
fn dispatch_bare_wikilink_is_link() {
let graph = build_graph(&["notes.md"]);
let emit = dispatch_wikilink_embed(
"notes",
None,
false,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Link(link) => {
assert!(link.contains("notes"));
assert!(link.contains("moss-resolved:"));
}
other => panic!("expected Link, got {:?}", other),
}
assert!(emit.outgoing_link.is_some());
assert!(emit.diagnostics.is_empty());
}
#[test]
fn dispatch_wikilink_with_alias_uses_alias_text() {
let graph = build_graph(&["notes.md"]);
let emit = dispatch_wikilink_embed(
"notes",
Some("My alias"),
false,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Link(link) => {
assert!(link.starts_with("[My alias]"));
}
other => panic!("expected Link, got {:?}", other),
}
}
#[test]
fn dispatch_unresolved_wikilink_emits_diagnostic() {
let graph = build_graph(&[]);
let emit = dispatch_wikilink_embed(
"missing",
None,
false,
&graph,
"index.md",
&empty_snapshot(),
);
assert_eq!(emit.diagnostics.len(), 1);
match emit.output {
EmitKind::Link(link) => assert!(link.contains("moss-unresolved:")),
other => panic!("expected Link, got {:?}", other),
}
}
#[test]
fn dispatch_anchor_wikilink_preserves_section_in_href() {
let graph = build_graph(&["notes.md"]);
let emit = dispatch_wikilink_embed(
"notes#section",
None,
false,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Link(link) => {
assert!(link.contains("moss-resolved:"));
assert!(link.contains("#section"), "got: {}", link);
}
other => panic!("expected Link, got {:?}", other),
}
}
#[test]
fn build_anchor_slugs_section_fragment() {
let graph = build_graph(&["notes.md"]);
let emit = dispatch_wikilink_embed(
"notes#My Heading",
None,
false,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Link(link) => {
assert!(link.contains("#my-heading"), "got: {}", link);
assert!(link.contains("moss-resolved:"), "got: {}", link);
}
other => panic!("expected Link, got {:?}", other),
}
}
#[test]
fn build_anchor_same_page_emits_bare_anchor() {
let graph = build_graph(&["notes.md"]);
let emit = dispatch_wikilink_embed(
"#My Heading",
None,
false,
&graph,
"notes.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Link(link) => {
assert!(link.contains("(#my-heading)"), "got: {}", link);
assert!(!link.contains("moss-resolved:"), "got: {}", link);
}
other => panic!("expected Link, got {:?}", other),
}
}
#[test]
fn build_anchor_block_ref_is_not_slugged() {
let graph = build_graph(&["notes.md"]);
let emit = dispatch_wikilink_embed(
"notes#^Block Id",
None,
false,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Link(link) => {
assert!(
link.contains("#Block Id"),
"expected raw block-ref, got: {}",
link
);
assert!(
!link.contains("#block-id"),
"block-ref was slugged: {}",
link
);
}
other => panic!("expected Link, got {:?}", other),
}
}
#[test]
fn dispatch_video_extension_routes_to_synth() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains("<video"), "expected <video>, got: {}", s);
assert!(s.contains(r#"src="/clip.mp4""#), "expected src=, got: {}", s);
assert!(s.contains("moss-embed-video"), "expected class, got: {}", s);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_pdf_extension_routes_to_synth() {
let graph = build_graph(&["report.pdf"]);
let emit = dispatch_wikilink_embed(
"report.pdf",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains("<object"), "expected <object>, got: {}", s);
assert!(
s.contains(r#"data="/report.pdf""#),
"expected data=, got: {}",
s
);
assert!(
s.contains(r#"type="application/pdf""#),
"expected type=, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_audio_extension_routes_to_synth() {
let graph = build_graph(&["song.mp3"]);
let emit = dispatch_wikilink_embed(
"song.mp3",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains("<audio"), "expected <audio>, got: {}", s);
assert!(s.contains(r#"src="/song.mp3""#), "expected src=, got: {}", s);
assert!(
s.contains(r#"type="audio/mpeg""#),
"expected MIME, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_iframe_extension_routes_to_synth() {
let graph = build_graph(&["widget.html"]);
let emit = dispatch_wikilink_embed(
"widget.html",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains("<iframe"), "expected <iframe>, got: {}", s);
assert!(
s.contains(r#"src="/widget.html""#),
"expected src=, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_model_extension_routes_to_synth() {
let graph = build_graph(&["scene.glb"]);
let emit = dispatch_wikilink_embed(
"scene.glb",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(
s.contains("<model-viewer"),
"expected <model-viewer>, got: {}",
s
);
assert!(
s.contains(r#"src="/scene.glb""#),
"expected src=, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_iframe_alias_carries_title() {
let graph = build_graph(&["widget.html"]);
let emit = dispatch_wikilink_embed(
"widget.html",
Some("Embedded Widget"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains(r#"title="Embedded Widget""#), "got: {}", s);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_video_sizing_alias_propagates_dims() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
Some("640x360"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains(r#"width="640px""#), "got: {}", s);
assert!(s.contains(r#"height="360px""#), "got: {}", s);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_only_fires_for_wikilink_caller() {
}
fn figure_of(emit: &WikilinkEmit) -> &crate::ast::node::Block {
match &emit.output {
EmitKind::Block(b) => b.as_ref(),
other => panic!("expected EmitKind::Block(Figure), got {other:?}"),
}
}
fn render_figure(emit: &WikilinkEmit) -> String {
let block = match emit.output.clone() {
EmitKind::Block(b) => *b,
other => panic!("expected EmitKind::Block(Figure), got {other:?}"),
};
let doc = crate::ast::Document::from_blocks(vec![block]);
crate::ast::render_document(&doc, &crate::ast::DefaultHooks::new())
}
fn dispatch_img(alias: Option<&str>) -> WikilinkEmit {
let graph = build_graph(&["photo.jpg", "hero.jpg"]);
dispatch_wikilink_embed(
"photo.jpg",
alias,
true,
&graph,
"index.md",
&empty_snapshot(),
)
}
#[test]
fn dispatch_image_plain_emits_figure_block() {
use crate::ast::node::{Block, Inline};
let emit = dispatch_img(None);
match figure_of(&emit) {
Block::Figure {
image,
caption,
width,
align,
class_names,
img_style,
} => {
assert!(caption.is_none(), "plain embed: no caption");
assert!(width.is_none());
assert!(align.is_none());
assert!(class_names.is_empty());
assert!(img_style.is_none());
match image {
Inline::Image {
src,
alt,
is_wikilink,
..
} => {
assert!(src.is_resolved());
assert_eq!(alt, "");
assert!(*is_wikilink);
}
other => panic!("expected Image, got {other:?}"),
}
}
other => panic!("expected Figure, got {other:?}"),
}
}
#[test]
fn dispatch_image_caption_text_keeps_alt_in_the_node_but_not_in_the_html() {
use crate::ast::node::{Block, Inline};
let emit = dispatch_img(Some("My caption"));
match figure_of(&emit) {
Block::Figure { image, caption, .. } => {
let cap = caption.as_ref().expect("caption present");
assert_eq!(cap.len(), 1);
match &cap[0] {
Inline::Text(t) => assert_eq!(t, "My caption"),
other => panic!("expected caption Text, got {other:?}"),
}
match image {
Inline::Image { alt, .. } => assert_eq!(alt, "My caption"),
other => panic!("expected Image, got {other:?}"),
}
}
other => panic!("expected Figure, got {other:?}"),
}
let html = render_figure(&emit);
assert!(html.contains(r#"alt="""#), "got: {html}");
assert!(
html.contains("<figcaption>My caption</figcaption>"),
"got: {html}"
);
}
#[test]
fn dispatch_image_width_token_preserved_as_data_width() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("wide"));
match figure_of(&emit) {
Block::Figure { width, caption, .. } => {
assert_eq!(width.as_deref(), Some("wide"));
assert!(caption.is_none(), "width token is not a caption");
}
other => panic!("expected Figure, got {other:?}"),
}
let html = render_figure(&emit);
assert!(html.contains(r#"data-width="wide""#), "got: {html}");
}
#[test]
fn dispatch_image_cover_emits_object_fit_on_inner_img() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("cover"));
match figure_of(&emit) {
Block::Figure {
img_style, caption, ..
} => {
assert_eq!(img_style.as_deref(), Some("object-fit:cover"));
assert!(caption.is_none(), "structural alias is not a caption");
}
other => panic!("expected Figure, got {other:?}"),
}
let html = render_figure(&emit);
assert!(html.contains("object-fit:cover"), "got: {html}");
assert!(
html.contains(r#"<figure class="moss-image""#),
"got: {html}"
);
}
#[test]
fn dispatch_image_cover_left_emits_fit_and_position() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("cover left"));
match figure_of(&emit) {
Block::Figure { img_style, .. } => {
let style = img_style.as_deref().expect("style present");
assert!(style.contains("object-fit:cover"), "got: {style}");
assert!(style.contains("object-position:left"), "got: {style}");
}
other => panic!("expected Figure, got {other:?}"),
}
}
#[test]
fn dispatch_image_params_form_emits_object_fit() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("fit=cover"));
match figure_of(&emit) {
Block::Figure { img_style, .. } => {
assert_eq!(img_style.as_deref(), Some("object-fit:cover"));
}
other => panic!("expected Figure, got {other:?}"),
}
}
#[test]
fn dispatch_image_two_word_position_combines() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("cover top left"));
match figure_of(&emit) {
Block::Figure { img_style, .. } => {
let style = img_style.as_deref().expect("style present");
assert!(style.contains("object-fit:cover"), "got: {style}");
assert!(style.contains("object-position:top left"), "got: {style}");
}
other => panic!("expected Figure, got {other:?}"),
}
}
#[test]
fn dispatch_image_wide_cover_combines_width_and_fit() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("wide cover"));
match figure_of(&emit) {
Block::Figure {
width, img_style, ..
} => {
assert_eq!(width.as_deref(), Some("wide"));
assert_eq!(img_style.as_deref(), Some("object-fit:cover"));
}
other => panic!("expected Figure, got {other:?}"),
}
let html = render_figure(&emit);
assert!(html.contains(r#"data-width="wide""#), "got: {html}");
assert!(html.contains("object-fit:cover"), "got: {html}");
}
#[test]
fn dispatch_image_inner_img_has_single_style_attr() {
let emit = dispatch_img(Some("fit=cover"));
let html = render_figure(&emit);
let n = html.matches("style=").count();
assert_eq!(n, 1, "exactly one style= attr, got {n}: {html}");
}
#[test]
fn wikilink_image_percent_carries_width() {
use crate::ast::node::Block;
let emit = dispatch_img(Some("55%"));
match figure_of(&emit) {
Block::Figure { width, caption, .. } => {
assert_eq!(width.as_deref(), Some("55%"));
assert!(caption.is_none(), "percent must not become a caption");
}
other => panic!("expected Figure, got {other:?}"),
}
}
#[test]
fn wikilink_image_percent_with_caption() {
use crate::ast::node::{Block, Inline};
let emit = dispatch_img(Some("My cap|55%"));
match figure_of(&emit) {
Block::Figure { width, caption, .. } => {
assert_eq!(width.as_deref(), Some("55%"));
let cap = caption.as_ref().expect("caption present");
assert!(
matches!(cap.as_slice(), [Inline::Text(t)] if t == "My cap"),
"caption should be the non-width segment, got {cap:?}"
);
}
other => panic!("expected Figure, got {other:?}"),
}
}
#[test]
fn dispatch_external_url_youtube_emits_html() {
let graph = build_graph(&[]);
let emit = dispatch_wikilink_embed(
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains("<iframe"), "got: {s}");
assert!(s.contains("youtube.com/embed"), "got: {s}");
assert!(s.contains(r#"data-provider="youtube""#), "got: {s}");
}
other => panic!("expected Html, got: {other:?}"),
}
assert!(
emit.outgoing_link.is_none(),
"external URLs must not register in ContentGraph"
);
assert!(emit.diagnostics.is_empty());
}
#[test]
fn dispatch_external_url_generic_emits_html() {
let graph = build_graph(&[]);
let emit = dispatch_wikilink_embed(
"https://example.com/embed",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains("<iframe"), "got: {s}");
assert!(s.contains(r#"src="https://example.com/embed""#), "got: {s}");
assert!(
!s.contains("data-provider="),
"generic must not have data-provider, got: {s}"
);
}
other => panic!("expected Html, got: {other:?}"),
}
assert!(emit.outgoing_link.is_none());
}
#[test]
fn dispatch_external_url_http_also_works() {
let graph = build_graph(&[]);
let emit = dispatch_wikilink_embed(
"http://example.com/embed",
None,
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => assert!(s.contains("<iframe"), "got: {s}"),
other => panic!("expected Html, got: {other:?}"),
}
}
#[test]
fn dispatch_external_url_with_width_pothole() {
let graph = build_graph(&[]);
let emit = dispatch_wikilink_embed(
"https://vimeo.com/123456789",
Some("wide"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => assert!(s.contains(r#"data-width="wide""#), "got: {s}"),
other => panic!("expected Html, got: {other:?}"),
}
}
#[test]
fn dispatch_video_loop_alias_emits_ambient_set() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
Some("loop"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains(" autoplay"), "missing autoplay, got: {}", s);
assert!(s.contains(" muted"), "missing muted, got: {}", s);
assert!(s.contains(" loop"), "missing loop, got: {}", s);
assert!(
s.contains(" playsinline"),
"missing playsinline, got: {}",
s
);
assert!(
!s.contains(" controls"),
"controls must be absent on loop branch, got: {}",
s
);
assert!(s.contains(" data-loop"), "missing data-loop, got: {}", s);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_video_loop_alias_case_insensitive() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
Some("LOOP"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(
s.contains(" autoplay"),
"missing autoplay on LOOP alias, got: {}",
s
);
assert!(
!s.contains(" controls"),
"controls must be absent on LOOP alias, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_video_size_and_loop_alias_propagates_all() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
Some("640x360 loop"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains(r#"width="640px""#), "missing width, got: {}", s);
assert!(
s.contains(r#"height="360px""#),
"missing height, got: {}",
s
);
assert!(s.contains(" autoplay"), "missing autoplay, got: {}", s);
assert!(s.contains(" data-loop"), "missing data-loop, got: {}", s);
assert!(
!s.contains(" controls"),
"controls must be absent, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_video_loop_first_then_size() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
Some("loop 640x360"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains(r#"width="640px""#), "missing width, got: {}", s);
assert!(
s.contains(r#"height="360px""#),
"missing height, got: {}",
s
);
assert!(s.contains(" autoplay"), "missing autoplay, got: {}", s);
}
other => panic!("expected Html, got: {:?}", other),
}
}
#[test]
fn dispatch_video_sizing_alias_still_works_without_loop() {
let graph = build_graph(&["clip.mp4"]);
let emit = dispatch_wikilink_embed(
"clip.mp4",
Some("640x360"),
true,
&graph,
"index.md",
&empty_snapshot(),
);
match emit.output {
EmitKind::Html(s) => {
assert!(s.contains(r#"width="640px""#), "missing width, got: {}", s);
assert!(
!s.contains(" autoplay"),
"autoplay must NOT be emitted without loop, got: {}",
s
);
assert!(
s.contains(" controls"),
"controls must be emitted on non-loop path, got: {}",
s
);
}
other => panic!("expected Html, got: {:?}", other),
}
}