tests: compare font styles case-insensitively and use instance attributes

This commit is contained in:
Madhav Agarwal
2026-10-29 13:28:31 +05:30
parent 90acacd917
commit bad35b0092
2 changed files with 43 additions and 17 deletions
+12 -10
View File
@@ -928,17 +928,19 @@ fn fonts_lists_bundled_and_installed_families() {
let r = s.execute("text.fonts", json!({"query": "noto ser", "rescan": true})).unwrap();
assert_eq!(r["families"][0]["family"], "Noto Serif");
// The bundled face is Regular; the OS can also provide Bold, Italic, or other faces.
// Compare against the faces the font database retained after the rescan: a system face
// whose family and primary style duplicate a bundled one is skipped.
let mut expected_styles = std::collections::BTreeSet::from(["Regular".to_string()]);
expected_styles.extend(
effectcraft_text::fonts::all_faces()
.iter()
.filter(|face| face.info.family.eq_ignore_ascii_case("Noto Serif"))
.flat_map(|face| std::iter::once(face.info.style.clone()).chain(face.info.instances.iter().map(|i| i.style.clone()))),
);
// Model `add_source()` on the independent directory inventory: a system face whose family
// and primary style duplicate an earlier face is skipped, with its named instances.
let mut expected_styles = std::collections::BTreeSet::from(["regular".to_string()]);
let mut retained = std::collections::BTreeSet::from([("noto serif".to_string(), "regular".to_string())]);
for face in installed.iter().filter(|face| face.family.eq_ignore_ascii_case("Noto Serif")) {
if retained.insert((face.family.to_ascii_lowercase(), face.style.to_ascii_lowercase())) {
expected_styles.insert(face.style.to_ascii_lowercase());
expected_styles.extend(face.instances.iter().map(|i| i.style.to_ascii_lowercase()));
}
}
let styles: Vec<String> = r["families"][0]["styles"].as_array().unwrap().iter().map(|style| style.as_str().unwrap().to_string()).collect();
assert_eq!(styles.iter().cloned().collect::<std::collections::BTreeSet<_>>(), expected_styles);
// `families()` keeps the first spelling of a style that differs only in case.
assert_eq!(styles.iter().map(|style| style.to_ascii_lowercase()).collect::<std::collections::BTreeSet<_>>(), expected_styles);
assert_eq!(styles.len(), expected_styles.len(), "style menu has no duplicates");
assert!(r["families"].as_array().unwrap().iter().all(|f| f["family"].as_str().unwrap().to_lowercase().contains("noto ser")));
// The style menus offer a family's own styles.
+31 -7
View File
@@ -108,14 +108,32 @@ fn bundled_families_and_resolution() {
.or_else(|| candidates.iter().find(|f| f.info.instances.iter().any(|i| i.style.eq_ignore_ascii_case("Bold Italic"))));
if let Some(exact) = exact {
assert_eq!(resolved.face, exact.id, "real style wins for {family}");
let expected_instance = if exact.info.style.eq_ignore_ascii_case("Bold Italic") {
None
} else {
exact.info.instances.iter().position(|i| i.style.eq_ignore_ascii_case("Bold Italic")).and_then(|i| u16::try_from(i).ok())
};
assert_eq!(resolved.instance, expected_instance, "exact style instance for {family}");
assert!(!resolved.synth_bold && !resolved.synth_italic);
} else {
// Italic mismatch dominates any supported font weight distance.
let distance = |f: &Face| u32::from(f.info.weight.abs_diff(700)) + if f.info.italic { 0 } else { 1_000 };
let nearest = candidates.iter().map(|f| distance(f)).min().unwrap();
assert_eq!(distance(&selected), nearest, "closest available style for {family}");
assert_eq!(resolved.synth_bold, selected.info.weight <= 500);
assert_eq!(resolved.synth_italic, !selected.info.italic);
// Italic mismatch dominates any supported font weight distance. Named instances
// resolve at their effective weight and slant, not the face's defaults.
let selected_instance = resolved.instance.and_then(|i| selected.info.instances.get(usize::from(i)));
let selected_weight = selected_instance.map_or(selected.info.weight, |i| i.weight(selected.info.weight));
let selected_italic = selected_instance.map_or(selected.info.italic, |i| i.italic(selected.info.italic));
let distance = |weight: u16, italic: bool| u32::from(weight.abs_diff(700)) + if italic { 0 } else { 1_000 };
let nearest = candidates
.iter()
.flat_map(|f| {
std::iter::once((f.info.weight, f.info.italic))
.chain(f.info.instances.iter().map(move |i| (i.weight(f.info.weight), i.italic(f.info.italic))))
})
.map(|(weight, italic)| distance(weight, italic))
.min()
.unwrap();
assert_eq!(distance(selected_weight, selected_italic), nearest, "closest available style for {family}");
assert_eq!(resolved.synth_bold, selected_weight <= 500);
assert_eq!(resolved.synth_italic, !selected_italic);
}
}
}
@@ -169,6 +187,7 @@ fn variable_named_instances_resolve_and_list() {
("Regular", 400.0, Some("EffectCraftInstanceTest-Regular")),
("Black", 900.0, Some("EffectCraftInstanceTest-Black")),
("Extra Bold", 800.0, None),
("Bold Italic", 700.0, Some("EffectCraftInstanceTest-BoldItalic")),
];
assert_eq!(add_font_data(crate::sfnt::test_font::variable(FAMILY, &instances, true)).len(), 1);
let wght = |r: &Resolved| instance_variations(r);
@@ -190,10 +209,15 @@ fn variable_named_instances_resolve_and_list() {
assert_eq!(wght(&ps), vec![("wght".to_string(), 900.0)]);
assert_eq!(wght(&resolve("EffectCraftInstanceTest-ExtraBold", "")), vec![("wght".to_string(), 800.0)]);
assert_eq!(wght(&resolve("effectcraft instance test extra bold", "")), vec![("wght".to_string(), 800.0)]);
// A named instance keeps its own index, so layout applies its axis values.
let bold_italic = resolve(FAMILY, "Bold Italic");
assert!(!bold_italic.missing && !bold_italic.synth_bold && !bold_italic.synth_italic);
assert_eq!((bold_italic.face, bold_italic.instance), (black.face, Some(4)));
assert_eq!(wght(&bold_italic), vec![("wght".to_string(), 700.0)]);
// The font menu lists every instance, in weight order.
let fams = families();
let styles = &fams.iter().find(|(f, _)| f == FAMILY).unwrap().1;
assert_eq!(styles, &["Light", "Regular", "Extra Bold", "Black"]);
assert_eq!(styles, &["Light", "Regular", "Extra Bold", "Black", "Bold Italic"]);
// Static fonts are untouched.
assert_eq!(resolve("Inter", "Bold").instance, None);
}