pack.rsannotatedpack.rssource138 lines · 5.9 KB · raw
1//! The allowlist held to the vendored pack. Reads the files, so it is a test, not library code.
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::path::PathBuf;
5
6use whiskers_icons::raster::Icon;
7use whiskers_icons::{IconError, IconId, Verdict, allowlist};
8
9fn pack() -> PathBuf {
10    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../third-party/pixel-icons")
11}
12
13fn files() -> BTreeSet<String> {
14    std::fs::read_dir(pack().join("icons"))
15        .expect("the vendored pack is there")
16        .map(|e| e.unwrap().file_name().into_string().unwrap())
17        .filter_map(|n| n.strip_suffix(".svg").map(str::to_owned))
18        .collect()
19}
20
21/// The category the pack's own README files each icon under.
22fn upstream_categories() -> BTreeMap<String, String> {
23    let text = std::fs::read_to_string(pack().join("UPSTREAM-README.md")).expect("the upstream README is kept");
24    let (mut cat, mut out) = (String::new(), BTreeMap::new());
25    for line in text.lines() {
26        if let Some(c) = line.strip_prefix("## ") {
27            cat = c.trim().to_owned();
28        } else if let Some(at) = line.find("icons/") {
29            if let Some(name) = line[at + 6..].split(".svg").next() {
30                out.insert(name.to_owned(), cat.clone());
31            }
32        }
33    }
34    out
35}
36
37#[test]
38fn the_allowlist_file_parses() {
39    assert!(allowlist().entries().len() > 300);
40}
41
42#[test]
43fn every_file_of_the_pack_is_decided_exactly_once_and_nothing_else_is() {
44    let decided: BTreeSet<String> = allowlist().entries().iter().map(|e| e.name.to_owned()).collect();
45    assert_eq!(decided.len(), allowlist().entries().len(), "no name twice");
46    let on_disk = files();
47    let undecided: Vec<_> = on_disk.difference(&decided).collect();
48    let phantom: Vec<_> = decided.difference(&on_disk).collect();
49    assert!(undecided.is_empty(), "icons nobody has decided: {undecided:?}");
50    assert!(phantom.is_empty(), "allowlisted names that are not files: {phantom:?}");
51}
52
53#[test]
54fn every_icon_sits_under_the_category_the_pack_files_it_under() {
55    let up = upstream_categories();
56    for e in allowlist().entries() {
57        assert_eq!(up.get(e.name).map(String::as_str), Some(e.category), "{} is in the wrong section", e.name);
58    }
59}
60
61#[test]
62fn no_category_excluded_whole_leaks_an_admitted_icon() {
63    let excluded: Vec<&str> = allowlist().categories().iter().filter(|c| c.excluded_because.is_some()).map(|c| c.name).collect();
64    let up = upstream_categories();
65    for id in IconId::all() {
66        let cat = up[id.as_str()].as_str();
67        assert!(!excluded.contains(&cat), "{id} is admitted but its category {cat} is excluded");
68    }
69}
70
71#[test]
72fn what_does_not_suit_a_young_child_is_excluded() {
73    for name in ["beer", "wine", "credit-card", "dollar", "paypal", "take-my-money", "stonks", "sad-pepe", "this-is-fine", "alert-triangle"] {
74        assert_eq!(IconId::new(name), Err(IconError::Excluded), "{name}");
75    }
76    // What she is likely to love is in.
77    for name in ["cat", "birthday-cake", "pikachu", "mario", "pacman", "charmander", "youtube", "spotify", "google", "rainbow", "unicorn"] {
78        assert!(IconId::new(name).is_ok(), "{name}");
79    }
80    assert_eq!(IconId::new("not-an-icon"), Err(IconError::Unknown));
81    assert_eq!(IconId::new(""), Err(IconError::Unknown));
82    assert_eq!(IconId::new("../cat"), Err(IconError::Unknown));
83}
84
85#[test]
86fn every_admitted_icon_rasterises_and_the_ids_round_trip_through_json() {
87    for id in IconId::all() {
88        let svg = std::fs::read_to_string(pack().join("icons").join(format!("{id}.svg"))).unwrap();
89        let icon = Icon::parse(&svg).unwrap_or_else(|e| panic!("{id}: {e}"));
90        assert_eq!(icon.size, 9, "{id}");
91        assert!(icon.colours().len() <= 36, "{id}");
92        let json = serde_json::to_string(&id).unwrap();
93        assert_eq!(serde_json::from_str::<IconId>(&json).unwrap(), id);
94    }
95    assert!(serde_json::from_str::<IconId>("\"beer\"").is_err(), "an excluded name does not parse");
96    assert!(serde_json::from_str::<IconId>("\"nope\"").is_err());
97}
98
99#[test]
100fn the_pack_is_vendored_unchanged() {
101    // The pack's own commit is recorded in its README; this holds the count so a stray edit or a lost file shows.
102    assert_eq!(files().len(), 357);
103    assert!(pack().join("LICENSE").exists(), "the pack's own file stays beside it");
104}
105
106#[test]
107fn the_counts_the_reviewer_reads_are_what_the_file_says() {
108    let admitted = allowlist().entries().iter().filter(|e| e.verdict == Verdict::Admitted).count();
109    assert_eq!(admitted, IconId::all().count());
110}
111
112#[test]
113fn a_known_icon_rasterises_to_the_pixels_it_should() {
114    // The pack's pencil, upright: an eraser, a body with a shaded side, a band and a point.
115    let svg = std::fs::read_to_string(pack().join("icons/pencil.svg")).unwrap();
116    let icon = Icon::parse(&svg).unwrap();
117    assert_eq!(
118        icon.ascii(),
119        ".........\n...000...\n...121...\n...121...\n...121...\n...121...\n...333...\n....4....\n........."
120    );
121}
122
123#[test]
124fn the_rasteriser_refuses_what_it_cannot_draw_faithfully() {
125    let wrap = |inner: &str| format!(r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9">{inner}</svg>"#);
126    for bad in [
127        r##"<polygon fill="#000000" points="0,0 3,1 0,2"/>"##,                 // a diagonal
128        r##"<path fill="#000000" d="M0,0Q1,1,2,0z"/>"##,                       // a quadratic curve
129        r##"<path fill="#000000" d="M0,0C1,1,2,2,3,0z"/>"##,                   // a real curve
130        r##"<rect fill="red" x="0" y="0" width="1" height="1"/>"##,            // a named colour
131        r##"<circle fill="#000000" cx="4" cy="4" r="2"/>"##,                   // another shape
132        r##"<rect x="0" y="0" width="1" height="1"/>"##,                       // no fill
133    ] {
134        assert!(Icon::parse(&wrap(bad)).is_err(), "{bad}");
135    }
136    assert!(Icon::parse(&wrap("")).is_err(), "nothing drawn");
137    assert!(Icon::parse(r##"<svg viewBox="0 0 9 8"><rect fill="#000000" width="1" height="1"/></svg>"##).is_err());
138}