tests.rs
raw
use super::*;
use crate::sexpr;
fn parse(source: &str) -> Result<Vec<RichRun>, Diagnostic> {
let doc = sexpr::read(source).expect("reads");
let form = &doc.values[0];
parse_rich_form(form.as_list().expect("list"), form.span()).expect("a rich form")
}
#[test]
fn nesting_is_the_structure() {
let runs = parse(r##"(rich "Hello " (bold "World") (color "#ff00ff" "!"))"##).unwrap();
assert_eq!(runs.len(), 3);
assert_eq!(runs[0].text, "Hello ");
assert!(!runs[0].bold);
assert_eq!(runs[1].text, "World");
assert!(runs[1].bold);
assert_eq!(runs[2].text, "!");
assert_eq!(runs[2].color, Some((0xff, 0x00, 0xff, 0xff)));
}
#[test]
fn styles_compose_by_nesting() {
// The reason nesting is worth having: neither form knows about the other.
let runs = parse(r##"(rich (bold (italic (color "#ff0000" "loud"))))"##).unwrap();
assert_eq!(runs.len(), 1);
assert!(runs[0].bold && runs[0].italic);
assert_eq!(runs[0].color, Some((0xff, 0, 0, 0xff)));
}
#[test]
fn a_style_covers_everything_inside_it() {
let runs = parse(r##"(rich (bold "one" (italic "two") "three"))"##).unwrap();
assert_eq!(runs.len(), 3);
assert!(runs.iter().all(|r| r.bold), "all of it is inside the bold");
assert_eq!(
runs.iter().filter(|r| r.italic).count(),
1,
"only the nested part is italic"
);
}
#[test]
fn size_and_the_rest() {
let runs = parse(r##"(rich (size 20 (underline (strike "x"))))"##).unwrap();
assert_eq!(runs[0].size, Some(20.0));
assert!(runs[0].underline && runs[0].strikethrough);
}
#[test]
fn a_link_is_a_style_form_like_any_other() {
let runs = parse(r##"(rich "See the " (link "home" "home page") " for more.")"##).unwrap();
assert_eq!(runs.len(), 3);
assert_eq!(runs[0].link, None, "prose before the link");
assert_eq!(runs[1].text, "home page");
assert_eq!(runs[1].link.as_deref(), Some("home"));
assert_eq!(runs[2].link, None, "prose after it");
}
#[test]
fn a_link_composes_with_the_styles_by_nesting() {
// The point of `link` being a style form: neither it nor `bold` knows the
// other exists, in either order.
for source in [
r##"(rich (link "home" (bold "Home")))"##,
r##"(rich (bold (link "home" "Home")))"##,
] {
let runs = parse(source).unwrap();
assert_eq!(runs.len(), 1, "{source}");
assert!(runs[0].bold, "{source}");
assert_eq!(runs[0].link.as_deref(), Some("home"), "{source}");
}
}
#[test]
fn a_link_covers_everything_inside_it() {
let runs = parse(r##"(rich (link "faq" "the " (italic "FAQ")))"##).unwrap();
assert_eq!(runs.len(), 2);
assert!(
runs.iter().all(|r| r.link.as_deref() == Some("faq")),
"all of it is inside the link",
);
}
#[test]
fn malformed_rich_text_is_pointed() {
for (source, want) in [
(r##"(rich)"##, "needs some text"),
(r##"(rich (bold))"##, "no text in it"),
(r##"(rich (blink "x"))"##, "not a rich-text style"),
// A link with no target at all…
(r##"(rich (link))"##, "takes a target first"),
// …and one whose target is not a string. Both point at a span: the
// form when nothing was written where the target belongs, what *was*
// written when something was.
(r##"(rich (link 3 "x"))"##, "takes a target first"),
// A target but no text is the same case as `(bold)`.
(r##"(rich (link "home"))"##, "no text in it"),
(r##"(rich (color "nope" "x"))"##, "is not a color"),
// A string first *is* read as the colour, so this is the no-colour case.
(r##"(rich (color 3 "x"))"##, "takes a color first"),
(r##"(rich (color "#fff"))"##, "no text in it"),
(r##"(rich (size "big" "x"))"##, "takes a number first"),
(r##"(rich 3)"##, "a string or a style form"),
] {
let error = parse(source).expect_err(source);
assert!(
error.message.contains(want),
"`{source}`: wanted {want:?}, got: {}",
error.message
);
}
}
#[test]
fn a_bad_link_target_points_at_the_target() {
// A diagnostic that points at the form when the mistake is *in* the form
// sends the reader looking. `(link 3 "x")` underlines the `3`.
let source = r##"(rich (link 3 "x"))"##;
let error = parse(source).expect_err(source);
assert_eq!(
&source[error.span.start..error.span.end],
"3",
"the offending target is what is underlined",
);
// With nothing written where the target belongs, the form itself is the
// only honest thing to point at.
let source = r##"(rich (link))"##;
let error = parse(source).expect_err(source);
assert_eq!(&source[error.span.start..error.span.end], "(link)");
}