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)");
}