use cooklang::quantity::{GroupedQuantity, Quantity};
pub fn ordered_components(grouped: &GroupedQuantity) -> Vec<&Quantity> {
let mut components: Vec<&Quantity> = grouped.iter().collect();
components.sort_by(|a, b| unit_key(a).cmp(unit_key(b)));
components
}
pub fn grouped_quantity_fmt(grouped: &GroupedQuantity) -> String {
ordered_components(grouped)
.into_iter()
.map(|q| q.to_string())
.collect::<Vec<_>>()
.join(", ")
}
fn unit_key(qty: &Quantity) -> &str {
qty.unit().unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::PARSER;
fn group(quantities: &[Quantity]) -> GroupedQuantity {
let mut grouped = GroupedQuantity::empty();
for qty in quantities {
grouped.add(qty, PARSER.converter());
}
grouped
}
fn qty(value: f64, unit: Option<&str>) -> Quantity {
Quantity::new(
cooklang::quantity::Value::Number(value.into()),
unit.map(|u| u.to_string()),
)
}
#[test]
fn inconvertible_units_render_in_unit_name_order() {
let cup_first = group(&[qty(1.0, Some("cup")), qty(100.0, Some("g"))]);
let gram_first = group(&[qty(100.0, Some("g")), qty(1.0, Some("cup"))]);
assert_eq!(grouped_quantity_fmt(&cup_first), "1 cup, 100 g");
assert_eq!(grouped_quantity_fmt(&gram_first), "1 cup, 100 g");
}
#[test]
fn the_order_does_not_vary_between_groups() {
let rendered: Vec<String> = (0..64)
.map(|_| {
grouped_quantity_fmt(&group(&[
qty(1.0, Some("sprig")),
qty(2.0, Some("clove")),
qty(3.0, Some("knob")),
qty(4.0, Some("glug")),
]))
})
.collect();
assert!(
rendered
.iter()
.all(|r| r == "2 clove, 4 glug, 3 knob, 1 sprig"),
"every group must render identically: {rendered:?}"
);
}
#[test]
fn the_unitless_component_comes_first() {
let grouped = group(&[qty(2.0, Some("g")), qty(3.0, None), qty(1.0, Some("cup"))]);
assert_eq!(grouped_quantity_fmt(&grouped), "3, 1 cup, 2 g");
}
#[test]
fn an_empty_group_renders_as_nothing() {
assert_eq!(grouped_quantity_fmt(&GroupedQuantity::empty()), "");
assert!(ordered_components(&GroupedQuantity::empty()).is_empty());
}
}