document_test.rs
#[cfg(test)]
mod tests {
use super::*;
const DOC: &str = "\
Title\n\
=====\n\
\n\
Some prose.\n\
\n\
[testmark]:# (one/input)\n\
```text\n\
hello\n\
world\n\
```\n\
\n\
More prose, and an untagged block that must be ignored:\n\
\n\
```sh\n\
not a fixture\n\
```\n\
\n\
[testmark]:# (one/output)\n\
```\n\
```\n";
#[test]
fn parses_hunks_and_ignores_untagged_blocks() {
let doc = Document::parse(DOC).expect("parses");
assert_eq!(doc.hunks.len(), 2);
assert_eq!(doc.hunk("one/input").unwrap().body, "hello\nworld\n");
assert_eq!(doc.hunk("one/input").unwrap().block_tag, "text");
assert_eq!(doc.hunk("one/output").unwrap().body, "");
}
#[test]
fn patch_roundtrip_is_identity() {
let doc = Document::parse(DOC).expect("parses");
let same = doc.patched("one/input", "hello\nworld\n").unwrap();
assert_eq!(same, DOC);
}
#[test]
fn patch_replaces_only_the_target_hunk() {
let doc = Document::parse(DOC).expect("parses");
let patched = doc.patched("one/output", "fresh content\n").unwrap();
let redoc = Document::parse(&patched).expect("reparses");
assert_eq!(redoc.hunk("one/output").unwrap().body, "fresh content\n");
assert_eq!(redoc.hunk("one/input").unwrap().body, "hello\nworld\n");
assert!(patched.contains("not a fixture"));
assert!(patched.contains("Some prose."));
}
#[test]
fn patch_can_empty_a_hunk() {
let doc = Document::parse(DOC).expect("parses");
let patched = doc.patched("one/input", "").unwrap();
let redoc = Document::parse(&patched).expect("reparses");
assert_eq!(redoc.hunk("one/input").unwrap().body, "");
}
#[test]
fn patch_of_unknown_hunk_is_none() {
let doc = Document::parse(DOC).expect("parses");
assert_eq!(doc.patched("no/such/hunk", "x\n"), None);
}
#[test]
fn comment_lookalikes_inside_fences_are_inert() {
// A `[testmark]:#` line inside a fenced block is content, not an annotation
// (this is what lets fixture files document the testmark format itself).
let doc = Document::parse("```\n[testmark]:# (ghost)\n```\n").expect("parses");
assert_eq!(doc.hunks.len(), 0);
}
#[test]
fn comment_trailing_content_is_reserved_and_ignored() {
// The spec reserves everything after the first whitespace inside the parens.
let doc = Document::parse("[testmark]:# (a future extension)\n```\nx\n```\n")
.expect("parses");
assert_eq!(doc.hunks.len(), 1);
assert_eq!(doc.hunks[0].name, "a");
}
#[test]
fn empty_document_parses() {
let doc = Document::parse("").expect("parses");
assert_eq!(doc.hunks.len(), 0);
}
#[test]
fn rejects_dangling_comment() {
let err = Document::parse("[testmark]:# (a)\nno fence here\n").unwrap_err();
assert_eq!(
err,
ParseError::DanglingComment {
line: 1,
}
);
}
#[test]
fn rejects_comment_at_end_of_file() {
// Dangling in the harshest way: nothing at all after the comment.
let err = Document::parse("[testmark]:# (a)").unwrap_err();
assert_eq!(
err,
ParseError::DanglingComment {
line: 1,
}
);
}
#[test]
fn rejects_malformed_comment() {
let err = Document::parse("[testmark]:# missing-parens\n```\n```\n").unwrap_err();
assert_eq!(
err,
ParseError::MalformedComment {
line: 1,
}
);
}
#[test]
fn rejects_empty_name() {
let err = Document::parse("[testmark]:# ()\n```\n```\n").unwrap_err();
assert_eq!(
err,
ParseError::MalformedComment {
line: 1,
}
);
}
#[test]
fn rejects_unclosed_fence() {
let err = Document::parse("[testmark]:# (a)\n```\nstuff\n").unwrap_err();
assert_eq!(
err,
ParseError::UnclosedFence {
line: 2,
}
);
}
#[test]
fn rejects_unclosed_untagged_fence() {
let err = Document::parse("prose\n```\nstuff\n").unwrap_err();
assert_eq!(
err,
ParseError::UnclosedFence {
line: 2,
}
);
}
#[test]
fn rejects_duplicate_names() {
let doc = "[testmark]:# (a)\n```\nx\n```\n[testmark]:# (a)\n```\ny\n```\n";
let err = Document::parse(doc).unwrap_err();
assert_eq!(
err,
ParseError::DuplicateName {
name: "a".into(),
line: 5,
}
);
}
#[test]
fn normalizes_crlf() {
let doc = Document::parse("[testmark]:# (a)\r\n```\r\nx\r\n```\r\n").expect("parses");
assert_eq!(doc.hunk("a").unwrap().body, "x\n");
}
#[test]
fn error_display_uses_one_based_lines() {
let err = Document::parse("prose\n[testmark]:# (a)\nno fence\n").unwrap_err();
assert_eq!(
err.to_string(),
"line 2: testmark comment not followed by a code fence"
);
}
}
crates/testmark/src/document_test.rs · 4.4 KiB · 820b6fe5c2