parse-srcset 0.1.0

Parse an HTML img srcset attribute into image candidates (url + density/width/height). A faithful port of the WHATWG-based parse-srcset npm package. Zero dependencies, no_std.
Documentation
//! Behavioral spec for `parse-srcset`, cross-checked against the npm package.

use parse_srcset::{parse_srcset, ImageCandidate};

fn c(url: &str, density: Option<f64>, width: Option<u64>, height: Option<u64>) -> ImageCandidate {
    ImageCandidate {
        url: url.into(),
        density,
        width,
        height,
    }
}

#[test]
fn url_only() {
    assert_eq!(parse_srcset("foo.png"), [c("foo.png", None, None, None)]);
    assert_eq!(
        parse_srcset("only-url.png"),
        [c("only-url.png", None, None, None)]
    );
}

#[test]
fn density() {
    assert_eq!(
        parse_srcset("foo.png 1x"),
        [c("foo.png", Some(1.0), None, None)]
    );
    assert_eq!(
        parse_srcset("foo.png 1.5x"),
        [c("foo.png", Some(1.5), None, None)]
    );
    assert_eq!(
        parse_srcset("a.png 0.5x"),
        [c("a.png", Some(0.5), None, None)]
    );
    assert_eq!(
        parse_srcset("foo.png 2x, bar.png 1x"),
        [
            c("foo.png", Some(2.0), None, None),
            c("bar.png", Some(1.0), None, None)
        ]
    );
}

#[test]
fn width_and_height() {
    assert_eq!(
        parse_srcset("a.jpg 100w, b.jpg 200w"),
        [
            c("a.jpg", None, Some(100), None),
            c("b.jpg", None, Some(200), None)
        ]
    );
    assert_eq!(
        parse_srcset("huge.jpg 1000000w"),
        [c("huge.jpg", None, Some(1_000_000), None)]
    );
    assert_eq!(
        parse_srcset("a.png 1w 2h"),
        [c("a.png", None, Some(1), Some(2))]
    );
}

#[test]
fn mixed_list_with_fallback() {
    assert_eq!(
        parse_srcset("x.jpg 480w, y.jpg 800w, z.jpg"),
        [
            c("x.jpg", None, Some(480), None),
            c("y.jpg", None, Some(800), None),
            c("z.jpg", None, None, None),
        ]
    );
}

#[test]
fn whitespace_and_empty_candidates() {
    assert_eq!(
        parse_srcset("  spaced.png   2x  "),
        [c("spaced.png", Some(2.0), None, None)]
    );
    assert_eq!(
        parse_srcset("a.png 1x, , b.png 2x"),
        [
            c("a.png", Some(1.0), None, None),
            c("b.png", Some(2.0), None, None)
        ]
    );
    assert_eq!(parse_srcset(""), Vec::<ImageCandidate>::new());
    assert_eq!(parse_srcset("   "), Vec::<ImageCandidate>::new());
}

#[test]
fn commas_in_url() {
    assert_eq!(
        parse_srcset("foo,bar.png 1x"),
        [c("foo,bar.png", Some(1.0), None, None)]
    );
    assert_eq!(
        parse_srcset("data:image/png;base64,iVBOR 1x"),
        [c("data:image/png;base64,iVBOR", Some(1.0), None, None)]
    );
}

#[test]
fn invalid_descriptors_are_skipped() {
    assert!(parse_srcset("a.png 100w 2x").is_empty()); // width + density
    assert!(parse_srcset("a.png -1x").is_empty()); // negative density
    assert!(parse_srcset("x.png 2X").is_empty()); // uppercase X
                                                  // a valid candidate alongside an invalid one is still returned
    assert_eq!(
        parse_srcset("good.png 1x, bad.png 1w 2x, good2.png 2x"),
        [
            c("good.png", Some(1.0), None, None),
            c("good2.png", Some(2.0), None, None)
        ]
    );
}