use super::{
planes::planes_gear_and_reference_angle,
shared::{count_angles_for_mode, parse_catalogue_angle_deg, sides_from_rows},
};
use crate::AngleItem;
use indicatrix::geometry::plane::GpuFacetPlane;
use slint::{ModelRc, VecModel};
#[test]
fn parse_catalogue_angle_deg_strips_the_degree_sign() {
assert_eq!(parse_catalogue_angle_deg("42.70\u{b0}"), Some(42.70));
}
#[test]
fn parse_catalogue_angle_deg_also_accepts_plain_text_with_no_degree_sign() {
assert_eq!(parse_catalogue_angle_deg("42.70"), Some(42.70));
}
#[test]
fn parse_catalogue_angle_deg_strips_a_mangled_degree_sign_too() {
assert_eq!(parse_catalogue_angle_deg("43.00\u{fffd}"), Some(43.00));
}
#[test]
fn parse_catalogue_angle_deg_is_none_for_unparsable_text() {
assert_eq!(parse_catalogue_angle_deg("not a number"), None);
}
fn row<'a>(facet: &'a str, angle: &'a str, index_val: &'a str) -> (&'a str, &'a str, &'a str) {
(facet, angle, index_val)
}
#[test]
fn classifies_real_detail_16() {
let rows = [
row("G", "90.00\u{b0}", "04-12-20"),
row("1", "47.00\u{b0}", "04-12-20"),
row("2", "43.00\u{b0}", "08-24-40"),
row("A", "50.00\u{b0}", "04-12-20"),
row("B", "35.00\u{b0}", "96-16-32"),
row("C", "15.00\u{b0}", "08-24-40"),
row("T", "0.00\u{b0}", "Table"),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0, -1, -1, 1, 1, 1, 1]);
}
#[test]
fn classifies_real_detail_47_with_an_empty_girdle_label() {
let rows = [
row("", "90.00\u{b0}", "96-08-16"),
row("1", "43.00\u{b0}", "96-08-16"),
row("2", "41.00\u{b0}", "02-06-10"),
row("A", "36.00\u{b0}", "96-08-16"),
row("B", "34.16\u{b0}", "02-06-10"),
row("T", "0.00\u{b0}", "Table"),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0, -1, -1, 1, 1, 1]);
}
#[test]
fn classifies_real_detail_1001() {
let rows = [
row("a", "44.00\u{b0}", "02-22-26"),
row("b", "44.00\u{b0}", "08-16-32"),
row("c", "34.00\u{b0}", "02-22-26"),
row("d", "34.00\u{b0}", "08-16-32"),
row("e", "24.00\u{b0}", "02-22-26"),
row("f", "24.00\u{b0}", "08-16-32"),
row("g", "0.00\u{b0}", "Table"),
row("1", "41.00\u{b0}", "96-24-48"),
row("2", "42.74\u{b0}", "03-21-27"),
row("3", "43.33\u{b0}", "06-18-30"),
row("4", "43.00\u{b0}", "08-16-32"),
row("5", "60.00\u{b0}", "02-22-26"),
row("G1", "90.00\u{b0}", "02-22-26"),
row("G2", "90.00\u{b0}", "08-16-32"),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 0, 0]);
}
#[test]
fn classifies_real_detail_2001() {
let rows = [
row("a", "37.00\u{b0}", "64-09-18"),
row("b", "45.00\u{b0}", "02-07-11"),
row("c", "27.00\u{b0}", "02-07-11"),
row("d", "24.48\u{b0}", "29-35"),
row("e", "27.00\u{b0}", "39-44-48"),
row("f", "0.00\u{b0}", "Table"),
row("1", "43.00\u{b0}", "64-09-18"),
row("2", "44.00\u{b0}", "02-07-11"),
row("G1", "90.00\u{b0}", "02-07-11"),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![1, 1, 1, 1, 1, 1, -1, -1, 0]);
}
#[test]
fn classifies_the_p_n_and_c_n_facet_convention() {
let rows = [
row("P1", "43.00\u{b0}", ""),
row("P2(G)", "44.00\u{b0}", ""),
row("pf3", "45.00\u{b0}", ""),
row("PF1", "46.00\u{b0}", ""),
row("C1", "15.00\u{b0}", ""),
row("C1A", "16.00\u{b0}", ""),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![-1, -1, -1, -1, 1, 1]);
}
#[test]
fn classifies_digit_plus_trailing_g_as_girdle_not_pavilion() {
let rows = [row("1G", "80.00\u{b0}", "")];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0]);
}
#[test]
fn positional_fallback_treats_the_tables_side_of_the_girdle_as_crown() {
let rows = [
row("", "90.00\u{b0}", ""), row("", "47.00\u{b0}", ""), row("", "43.00\u{b0}", ""), row("", "50.00\u{b0}", ""), row("", "0.00\u{b0}", "Table"),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0, 1, 1, 1, 1]);
}
#[test]
fn positional_fallback_without_a_table_row_uses_before_after_girdle_when_girdle_is_in_the_middle() {
let rows = [
row("", "40.00\u{b0}", ""),
row("", "90.00\u{b0}", ""), row("", "41.00\u{b0}", ""),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![1, 0, -1]);
}
#[test]
fn positional_fallback_without_a_table_row_reports_zero_when_girdle_is_first() {
let rows = [
row("", "90.00\u{b0}", ""), row("", "41.00\u{b0}", ""),
row("", "42.00\u{b0}", ""),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0, 0, 0]);
}
#[test]
fn positional_fallback_without_a_table_row_reports_zero_when_girdle_is_last() {
let rows = [
row("", "41.00\u{b0}", ""),
row("", "42.00\u{b0}", ""),
row("", "90.00\u{b0}", ""), ];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0, 0, 0]);
}
#[test]
fn sides_from_rows_reports_zero_for_every_girdle_row_not_only_the_first() {
let rows = [
row("A", "40.00\u{b0}", ""),
row("", "90.00\u{b0}", ""),
row("", "90.00\u{b0}", ""),
row("1", "41.00\u{b0}", ""),
];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![1, 0, 0, -1]);
}
#[test]
fn a_steep_but_not_exactly_90_degree_facet_is_not_the_girdle() {
let rows = [row("A", "89.60\u{b0}", "")];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![1]);
}
#[test]
fn sides_from_rows_reports_zero_for_unparsable_angle_text_with_no_usable_label() {
let rows = [row("", "not a number", ""), row("", "41.00\u{b0}", "")];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides[0], 0);
}
#[test]
fn sides_from_rows_reports_zero_with_no_girdle_marker_and_no_usable_label() {
let rows = [row("", "34.50\u{b0}", ""), row("", "42.70\u{b0}", "")];
let sides = sides_from_rows(rows.into_iter());
assert_eq!(sides, vec![0, 0]);
}
#[test]
fn falls_back_to_the_placeholder_reconstruction_with_no_real_design() {
let angle_items = [AngleItem {
order_idx: 0,
side: -1,
facet: "P1".into(),
angle: "-41.0".into(),
index_val: "0, 24, 48, 72".into(),
notes: "".into(),
}];
let (planes, gear_teeth, reference_angle) =
planes_gear_and_reference_angle(None, Some("Round"), Some("96"), &angle_items);
assert_ne!(planes.len(), 0);
assert_eq!(gear_teeth, 96);
assert_eq!(reference_angle, 0.0);
}
#[test]
fn prefers_the_real_designs_own_planes_and_reference_angle_when_resolved() {
let resolved_planes = vec![GpuFacetPlane::new(glam::Vec3::Y, -1.0)];
let (planes, gear_teeth, reference_angle) = planes_gear_and_reference_angle(
Some((resolved_planes.clone(), 12, 2.5)),
Some("Round"),
Some("999"),
&[],
);
assert_eq!(planes.len(), resolved_planes.len());
assert_eq!(gear_teeth, 12);
assert_eq!(reference_angle, 2.5);
}
fn angle_item(side: i32) -> AngleItem {
AngleItem {
order_idx: 0,
side,
facet: "P1".into(),
angle: "0.0".into(),
index_val: "".into(),
notes: "".into(),
}
}
#[test]
fn mode_all_counts_every_row() {
let angles = ModelRc::new(VecModel::from(vec![
angle_item(-1),
angle_item(1),
angle_item(0),
]));
assert_eq!(count_angles_for_mode(&angles, 0), 3);
}
#[test]
fn mode_pavilion_counts_only_negative_side_rows() {
let angles = ModelRc::new(VecModel::from(vec![
angle_item(-1),
angle_item(-1),
angle_item(1),
angle_item(0),
]));
assert_eq!(count_angles_for_mode(&angles, 1), 2);
}
#[test]
fn mode_crown_counts_only_positive_side_rows() {
let angles = ModelRc::new(VecModel::from(vec![
angle_item(-1),
angle_item(1),
angle_item(1),
angle_item(0),
]));
assert_eq!(count_angles_for_mode(&angles, 2), 2);
}
#[test]
fn empty_angle_list_counts_to_zero_under_every_mode() {
let angles: ModelRc<AngleItem> = ModelRc::new(VecModel::from(Vec::<AngleItem>::new()));
for mode in [0, 1, 2] {
assert_eq!(count_angles_for_mode(&angles, mode), 0);
}
}
#[test]
fn filtered_row_count_agrees_with_sides_from_rows_on_a_real_schedule() {
let rows = [
row("G", "90.00\u{b0}", ""),
row("1", "47.00\u{b0}", ""),
row("2", "43.00\u{b0}", ""),
row("A", "50.00\u{b0}", ""),
row("B", "35.00\u{b0}", ""),
row("C", "15.00\u{b0}", ""),
row("T", "0.00\u{b0}", "Table"),
];
let sides = sides_from_rows(rows.into_iter());
let angles = ModelRc::new(VecModel::from(
sides.into_iter().map(angle_item).collect::<Vec<_>>(),
));
assert_eq!(count_angles_for_mode(&angles, 0), 7); assert_eq!(count_angles_for_mode(&angles, 1), 2); assert_eq!(count_angles_for_mode(&angles, 2), 4); }