use laterite_core::icons::IconSet;
#[derive(Clone, Copy)]
pub(crate) struct Icons<'a> {
pub set: &'a IconSet,
pub sprite: &'a str,
}
impl<'a> Icons<'a> {
pub fn new(set: &'a IconSet, sprite: &'a str) -> Self {
Self { set, sprite }
}
pub fn render(&self, name: Option<&str>) -> String {
svg(self.set, self.sprite, name)
}
}
pub(crate) fn svg(set: &IconSet, sprite_url: &str, name: Option<&str>) -> String {
match name {
Some(name) => set.use_ref(name, sprite_url, None).unwrap_or_else(|| {
tracing::warn!(icon = name, "unknown icon name; rendering the missing mark");
MISSING.to_string()
}),
None => String::new(),
}
}
const MISSING: &str = concat!(
r#"<svg class="lat-icon lat-icon--missing" viewBox="0 0 24 24" fill="none" "#,
r#"stroke="currentColor" stroke-width="2" stroke-linecap="round" "#,
r#"stroke-linejoin="round" aria-hidden="true" focusable="false">"#,
r#"<rect x="3" y="3" width="18" height="18" rx="2" stroke-dasharray="3 3"/>"#,
r#"<path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>"#
);
pub(crate) fn require(set: &IconSet, what: &str, name: Option<&str>) {
let Some(name) = name else { return };
if set.has(name) {
return;
}
let hint = match set.nearest(name, 3) {
hits if hits.is_empty() => String::new(),
hits => format!("; did you mean {}?", hits.join(", ")),
};
panic!(
"{what} names the icon `{name}`, which is not in the set{hint} \
See docs/src/reference/icons.md for the full list, or register your \
own with `add_icon`."
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_known_name_renders_its_glyph() {
let set = IconSet::new();
let markup = svg(&set, "/s.svg", Some("users"));
assert!(markup.contains("<svg"));
assert!(markup.contains("#lat-users"));
assert!(!markup.contains("lat-icon--missing"));
}
#[test]
fn an_unknown_runtime_name_is_visibly_missing() {
let set = IconSet::new();
assert!(svg(&set, "/s.svg", Some("no-such-icon")).contains("lat-icon--missing"));
}
#[test]
fn no_icon_renders_nothing() {
assert_eq!(svg(&IconSet::new(), "/s.svg", None), "");
}
#[test]
#[should_panic(expected = "did you mean")]
fn a_descriptor_typo_stops_the_boot_with_a_suggestion() {
require(&IconSet::new(), "settings item `acme.blog`", Some("userss"));
}
#[test]
fn a_known_descriptor_name_passes() {
require(&IconSet::new(), "settings item `acme.blog`", Some("users"));
require(&IconSet::new(), "settings item `acme.blog`", None);
}
}