mod cst;
use super::check;
use crate::{SyntaxKind, parse};
#[test]
fn simple_choice() {
check("* Choice text\n");
}
#[test]
fn sticky_choice() {
check("+ Choice text\n");
}
#[test]
fn nested_choice() {
check("* * Nested choice\n");
}
#[test]
fn choice_with_bracket() {
check("* [hidden] shown\n");
}
#[test]
fn choice_with_label() {
check("* (myLabel) Choice text\n");
}
#[test]
fn choice_with_condition() {
check("* {x > 5} Choice text\n");
}
#[test]
fn choice_with_divert() {
check("* Choice -> knot\n");
}
#[test]
fn choice_with_tags() {
check("* Choice #tag1\n");
}
#[test]
fn choice_three_regions() {
check("* Start[middle]end\n");
}
#[test]
fn double_plus_choice() {
let p = parse("++[text] inner\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let dbg = format!("{:#?}", p.syntax());
assert!(dbg.contains("CHOICE@"), "expected CHOICE node, got:\n{dbg}");
}
#[test]
fn triple_plus_choice() {
let p = parse("+++[text] deep\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let dbg = format!("{:#?}", p.syntax());
assert!(dbg.contains("CHOICE@"), "expected CHOICE node, got:\n{dbg}");
}
#[test]
fn double_plus_choice_in_knot() {
let p = parse("== k ==\n+[a] Hello\n++[b] World\n+++[c] Deep\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let dbg = format!("{:#?}", p.syntax());
let choice_count = dbg.matches("CHOICE@").count();
assert_eq!(
choice_count, 3,
"expected 3 CHOICE nodes, got {choice_count}:\n{dbg}"
);
}
#[test]
fn insta_choice_with_bracket() {
let p = parse("* Hello[hidden]world\n");
insta::assert_snapshot!(format!("{:#?}", p.syntax()));
}
#[test]
fn insta_choice_with_condition() {
let p = parse("* {visited} Been here.\n");
insta::assert_snapshot!(format!("{:#?}", p.syntax()));
}
#[test]
fn block_comment_in_choice_start_content_stays_one_choice() {
let src = "* Hello /* c */ world\n";
let p = parse(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let choices: Vec<_> = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::CHOICE)
.collect();
assert_eq!(choices.len(), 1, "expected exactly one CHOICE: {choices:?}");
let comment_inside_choice = choices[0]
.descendants_with_tokens()
.any(|c| c.kind() == SyntaxKind::BLOCK_COMMENT);
assert!(
comment_inside_choice,
"BLOCK_COMMENT should be nested inside the CHOICE"
);
let text_nodes: String = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::TEXT)
.map(|n| n.text().to_string())
.collect();
assert_eq!(
text_nodes, "Hello world",
"comment span alone should be elided, both adjoining spaces kept \
(double space), matching #2958's content.rs precedent"
);
}
#[test]
fn block_comment_in_choice_inner_content_stays_one_choice() {
let src = "* [opt] Hello /* c */ world\n";
let p = parse(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let choices: Vec<_> = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::CHOICE)
.collect();
assert_eq!(choices.len(), 1, "expected exactly one CHOICE: {choices:?}");
let inner_content = choices[0]
.descendants()
.find(|n| n.kind() == SyntaxKind::CHOICE_INNER_CONTENT)
.expect("expected a CHOICE_INNER_CONTENT node");
let text_nodes: String = inner_content
.descendants()
.filter(|n| n.kind() == SyntaxKind::TEXT)
.map(|n| n.text().to_string())
.collect();
assert_eq!(
text_nodes, " Hello world",
"comment span alone should be elided, both adjoining spaces kept, \
inside CHOICE_INNER_CONTENT"
);
}
#[test]
fn block_comment_between_interpolations_in_choice_text() {
let src = "* Hi {a} /* c */ {b}\n";
let p = parse(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let choices = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::CHOICE)
.count();
assert_eq!(choices, 1, "expected exactly one CHOICE");
let text_nodes: String = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::TEXT)
.map(|n| n.text().to_string())
.collect();
assert_eq!(
text_nodes, "Hi ",
"both spaces around the elided comment must survive between \
interpolations"
);
}
#[test]
fn line_comment_in_choice_text_unaffected_by_fix() {
let src = "* Hello // note\n";
let p = parse(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let choices = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::CHOICE)
.count();
assert_eq!(choices, 1, "expected exactly one CHOICE");
let text_nodes: String = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::TEXT)
.map(|n| n.text().to_string())
.collect();
assert_eq!(text_nodes, "Hello ", "text before the line comment only");
}
#[test]
fn block_comment_in_choice_bracket_content_already_works() {
let src = "* Hello[hidden /* c */ bracket]world\n";
let p = parse(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let choices = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::CHOICE)
.count();
assert_eq!(choices, 1, "expected exactly one CHOICE");
}
#[test]
fn stray_r_bracket_in_choice_inner_content_does_not_hang() {
let src = "* [opt] Hello ] world\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
}
#[test]
fn stray_r_brace_in_choice_text_does_not_hang() {
let src = "* Hello } world\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
}
#[test]
fn block_comment_directly_before_bracket_stays_one_choice() {
let src = "* Hello /* c */ [middle] end\n";
let p = parse(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let choices = p
.syntax()
.descendants()
.filter(|n| n.kind() == SyntaxKind::CHOICE)
.count();
assert_eq!(choices, 1, "expected exactly one CHOICE");
}