use crate::document::Document;
use crate::Sentinel;
#[test]
fn card_fence_parses_kind_fields_and_body() {
let src = "---\nQUILL: q\n---\n\n```card product\nname: Widget\nprice: 19\n```\n\nWidget description.\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 1);
let card = &doc.cards()[0];
assert_eq!(card.sentinel(), &Sentinel::Card("product".to_string()));
assert_eq!(card.tag(), "product");
assert_eq!(
card.frontmatter().get("name").unwrap().as_str(),
Some("Widget")
);
assert_eq!(card.body(), "\nWidget description.\n");
}
#[test]
fn card_fence_empty_body_has_no_fields() {
let src = "---\nQUILL: q\n---\n\n```card marker\n```\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 1);
assert!(doc.cards()[0].frontmatter().is_empty());
assert_eq!(doc.cards()[0].body(), "");
}
#[test]
fn card_fence_tilde_fence_accepted() {
let src = "---\nQUILL: q\n---\n\n~~~card product\nname: Widget\n~~~\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 1);
assert_eq!(doc.cards()[0].tag(), "product");
}
#[test]
fn card_fence_multiple_cards() {
let src =
"---\nQUILL: q\n---\n\n```card product\nname: A\n```\n\n```card product\nname: B\n```\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 2);
assert_eq!(
doc.cards()[0].frontmatter().get("name").unwrap().as_str(),
Some("A")
);
assert_eq!(
doc.cards()[1].frontmatter().get("name").unwrap().as_str(),
Some("B")
);
}
#[test]
fn new_and_legacy_syntax_parse_to_equal_documents() {
let new = "---\nQUILL: q\n---\n\n```card product\nname: Widget\nprice: 19\n```\n\nBody.\n";
let legacy =
"---\nQUILL: q\n---\n\n---\nCARD: product\nname: Widget\nprice: 19\n---\n\nBody.\n";
let from_new = Document::from_markdown(new).unwrap();
let from_legacy = Document::from_markdown(legacy).unwrap();
assert_eq!(from_new, from_legacy);
}
#[test]
fn mixed_new_and_legacy_cards_in_one_document() {
let src =
"---\nQUILL: q\n---\n\n```card product\nname: A\n```\n\n---\nCARD: product\nname: B\n---\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 2);
assert_eq!(doc.cards()[0].tag(), "product");
assert_eq!(doc.cards()[1].tag(), "product");
}
#[test]
fn emit_uses_canonical_card_fence_for_new_syntax() {
let src = "---\nQUILL: q\n---\n\n```card product\nname: Widget\n```\n";
let doc = Document::from_markdown(src).unwrap();
let emitted = doc.to_markdown();
assert_eq!(
emitted,
"---\nQUILL: q\n---\n\n```card product\nname: \"Widget\"\n```\n"
);
}
#[test]
fn legacy_syntax_round_trips_to_canonical_card_fence() {
let src = "---\nQUILL: q\n---\n\n---\nCARD: product\nname: Widget\n---\n";
let doc = Document::from_markdown(src).unwrap();
let emitted = doc.to_markdown();
assert!(
!emitted.contains("CARD:"),
"legacy fence must not survive emit:\n{emitted}"
);
assert!(emitted.contains("```card product\n"), "emit:\n{emitted}");
assert_eq!(
emitted,
"---\nQUILL: q\n---\n\n```card product\nname: \"Widget\"\n```\n"
);
}
#[test]
fn emit_is_idempotent_for_card_fences() {
let src = "---\nQUILL: q\n---\n\n```card product\nname: Widget\n```\n\nTrailing body.\n";
let doc = Document::from_markdown(src).unwrap();
let once = doc.to_markdown();
let twice = Document::from_markdown(&once).unwrap().to_markdown();
assert_eq!(once, twice);
}
#[test]
fn card_fence_body_round_trips() {
let src =
"---\nQUILL: q\n---\n\nMain body.\n\n```card product\nname: Widget\n```\n\nCard body.\n";
let a = Document::from_markdown(src).unwrap();
let b = Document::from_markdown(&a.to_markdown()).unwrap();
assert_eq!(a, b);
assert_eq!(a.main().body(), "\nMain body.\n");
assert_eq!(a.cards()[0].body(), "\nCard body.\n");
}
#[test]
fn card_fence_preserves_yaml_comments() {
let src = "---\nQUILL: q\n---\n\n```card product\n# a banner\nname: Widget\n```\n";
let doc = Document::from_markdown(src).unwrap();
let emitted = doc.to_markdown();
assert!(
emitted.contains("```card product\n# a banner\nname: \"Widget\"\n```\n"),
"emit:\n{emitted}"
);
let reparsed = Document::from_markdown(&emitted).unwrap();
assert_eq!(doc, reparsed);
}
#[test]
fn card_fence_missing_kind_is_error() {
let src = "---\nQUILL: q\n---\n\n```card\nname: Widget\n```\n";
let err = Document::from_markdown(src).unwrap_err().to_string();
assert!(err.contains("missing a card kind"), "got: {err}");
}
#[test]
fn card_fence_invalid_kind_is_error() {
let src = "---\nQUILL: q\n---\n\n```card BadKind\nname: Widget\n```\n";
let err = Document::from_markdown(src).unwrap_err().to_string();
assert!(err.contains("Invalid card kind"), "got: {err}");
}
#[test]
fn card_fence_extra_info_tokens_is_error() {
let src = "---\nQUILL: q\n---\n\n```card product extra\nname: Widget\n```\n";
let err = Document::from_markdown(src).unwrap_err().to_string();
assert!(err.contains("exactly ```card <kind>```"), "got: {err}");
}
#[test]
fn card_fence_unclosed_is_error() {
let src = "---\nQUILL: q\n---\n\n```card product\nname: Widget\n";
let err = Document::from_markdown(src).unwrap_err().to_string();
assert!(err.contains("not closed"), "got: {err}");
}
#[test]
fn card_fence_reserved_key_is_error() {
let src = "---\nQUILL: q\n---\n\n```card product\nBODY: nope\n```\n";
let err = Document::from_markdown(src).unwrap_err().to_string();
assert!(err.contains("Reserved field name"), "got: {err}");
}
#[test]
fn ordinary_code_fence_is_not_a_card() {
let src = "---\nQUILL: q\n---\n\n```rust\nlet x = 1;\n```\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 0);
assert!(doc.main().body().contains("```rust"));
}
#[test]
fn card_info_inside_outer_code_fence_is_not_a_card() {
let src = "---\nQUILL: q\n---\n\n````text\n```card product\nname: Widget\n```\n````\n";
let doc = Document::from_markdown(src).unwrap();
assert_eq!(doc.cards().len(), 0);
}
#[test]
fn card_fence_without_blank_line_above_is_not_a_card() {
let src = "---\nQUILL: q\n---\n\nSome prose.\n```card product\nname: Widget\n```\n";
let out = Document::from_markdown_with_warnings(src).unwrap();
assert_eq!(out.document.cards().len(), 0);
assert!(out
.warnings
.iter()
.any(|w| w.code.as_deref() == Some("parse::card_fence_missing_blank")));
}