use super::*;
#[test]
fn abs_step_matches_the_listed_examples() {
assert_eq!(abs_step('C', 4), 35);
assert_eq!(abs_step('b', 4), 41);
assert_eq!(abs_step('G', 5), 46);
assert_eq!(abs_step('D', 3), 29);
assert_eq!(abs_step('A', -1), 5);
}
#[test]
fn abs_step_maps_unknown_letters_to_c() {
assert_eq!(abs_step('x', 4), 35);
assert_eq!(abs_step('\u{1F3B5}', 4), 35);
}
#[test]
fn abs_step_is_safe_at_the_octave_extremes() {
assert_eq!(abs_step('B', 127), 902);
assert_eq!(abs_step('C', -128), -889);
}
#[test]
fn middle_line_steps_follow_sign_and_line() {
let cases = [
("G", "2", 41), ("G", "1", 43), ("F", "4", 29), ("F", "3", 31), ("C", "3", 35), ("C", "4", 33), ("C", "1", 39), ("C", "2", 37), ];
for (sign, line, middle) in cases {
assert_eq!(middle_step_for(sign, line), middle, "{sign}/{line}");
}
}
#[test]
fn percussion_reads_as_treble_on_any_line() {
for line in ["1", "2", "3", "5", ""] {
assert_eq!(middle_step_for("percussion", line), 41);
}
}
#[test]
fn bad_lines_and_unknown_signs_fall_back() {
assert_eq!(middle_step_for("G", ""), 41);
assert_eq!(middle_step_for("F", "0"), 29);
assert_eq!(middle_step_for("C", "6"), 35);
assert_eq!(middle_step_for("F", "four"), 29);
assert_eq!(middle_step_for("TAB", "5"), 41);
assert_eq!(middle_step_for("", ""), 41);
}
#[test]
fn middle_distance_matches_the_listed_examples() {
let treble = Some("treble");
let bass = Some("bass");
let alto = Some("alto");
let tenor = Some("tenor");
let perc = Some("perc");
assert_eq!(middle_distance(treble, 'B', 4), 0);
assert_eq!(middle_distance(treble, 'C', 4), 6);
assert_eq!(middle_distance(treble, 'G', 5), -5);
assert_eq!(middle_distance(treble, 'A', 3), 8);
assert_eq!(middle_distance(bass, 'D', 3), 0);
assert_eq!(middle_distance(bass, 'C', 4), -6);
assert_eq!(middle_distance(bass, 'G', 2), 4);
assert_eq!(middle_distance(alto, 'C', 4), 0);
assert_eq!(middle_distance(alto, 'F', 3), 4);
assert_eq!(middle_distance(tenor, 'A', 3), 0);
assert_eq!(middle_distance(tenor, 'C', 4), -2);
assert_eq!(middle_distance(perc, 'B', 4), 0);
assert_eq!(middle_distance(perc, 'E', 4), 4);
assert_eq!(middle_distance(None, 'b', 4), 0);
}
#[test]
fn middle_distance_is_safe_at_the_octave_extremes() {
assert_eq!(middle_distance(None, 'B', 127), 41 - 902);
assert_eq!(middle_distance(Some("bass"), 'C', -128), 29 + 889);
}
#[test]
fn half_and_shorter_carry_stems() {
for note_type in [
"half", "quarter", "eighth", "16th", "32nd", "64th", "128th", "256th", "512th", "1024th",
] {
assert!(note_type_has_stem(note_type), "{note_type}");
}
}
#[test]
fn whole_and_longer_and_unknown_types_have_no_stem() {
for note_type in [
"whole", "breve", "long", "maxima", "", "Quarter", "2048th", "eighth ",
] {
assert!(!note_type_has_stem(note_type), "{note_type:?}");
}
}
#[test]
fn stem_up_matches_the_st_table() {
type Case = (
&'static str,
&'static [i32],
Option<&'static [i32]>,
bool,
usize,
bool,
);
let cases: [Case; 15] = [
("ST1", &[6], None, false, 0, true),
("ST2", &[-5], None, false, 0, false),
("ST3", &[0], None, false, 0, false),
("ST4", &[3, -5], None, false, 0, false),
("ST5", &[5, -3], None, false, 0, true),
("ST6", &[4, -2, -4], None, false, 0, false),
("ST7", &[4, 2, -4], None, false, 0, true),
("ST8", &[4, -4], None, false, 0, false),
("ST9", &[1], Some(&[1, 2, -6]), false, 0, false),
("ST10", &[-1], Some(&[-1, 3, 4]), false, 0, true),
("ST11", &[-6], None, true, 0, true),
("ST12", &[6], None, true, 1, false),
("ST13", &[0], None, true, 2, true),
("ST14", &[0], None, true, 3, false),
("ST15", &[i32::MIN, i32::MAX], None, false, 0, false),
];
for (name, own, beam, multivoice, slot, expected) in cases {
assert_eq!(stem_up(own, beam, multivoice, slot), expected, "{name}");
}
}
#[test]
fn stem_up_edge_cases() {
assert!(!stem_up(&[0, 0], None, false, 0));
assert!(stem_up(&[6], Some(&[]), false, 0));
assert!(!stem_up(&[], None, false, 0));
assert!(!stem_up(&[], Some(&[]), false, 0));
assert!(stem_up(&[i32::MAX], None, false, 0));
assert!(!stem_up(&[i32::MIN], None, false, 0));
}
#[test]
fn every_member_of_a_beam_group_gets_the_same_direction() {
let group = [-1, 3, 4, -2];
let directions: Vec<bool> = group
.iter()
.map(|&own| stem_up(&[own], Some(&group), false, 0))
.collect();
assert_eq!(directions, [true, true, true, true]);
}
#[test]
fn multivoice_slot_parity_holds_for_huge_slots() {
assert!(!stem_up(&[-9], None, true, usize::MAX));
assert!(stem_up(&[9], None, true, usize::MAX - 1));
}
#[test]
fn rest_display_matches_the_table() {
let cases = [
("G", "2", ('F', 5), ('E', 4)),
("F", "4", ('A', 3), ('G', 2)),
("C", "3", ('G', 4), ('F', 3)),
("C", "4", ('E', 4), ('D', 3)),
("percussion", "2", ('F', 5), ('E', 4)),
("C", "1", ('D', 5), ('C', 4)),
("G", "1", ('A', 5), ('G', 4)),
];
let display = |middle: i32| {
let octave = i8::try_from(middle.div_euclid(7) - 1).unwrap_or(0);
let index = usize::try_from(middle.rem_euclid(7)).unwrap_or(0);
(STEP_LETTERS[index], octave)
};
for (sign, line, upper, lower) in cases {
let middle = middle_step_for(sign, line);
assert_eq!(display(middle + 4), upper, "{sign}/{line} upper");
assert_eq!(display(middle - 4), lower, "{sign}/{line} lower");
}
}
#[test]
fn rest_display_matches_the_listed_examples() {
assert_eq!(rest_display(None, 0), ('F', 5));
assert_eq!(rest_display(None, 1), ('E', 4));
assert_eq!(rest_display(Some("bass"), 0), ('A', 3));
assert_eq!(rest_display(Some("bass"), 1), ('G', 2));
assert_eq!(rest_display(Some("alto"), 0), ('G', 4));
assert_eq!(rest_display(Some("alto"), 1), ('F', 3));
assert_eq!(rest_display(Some("tenor"), 1), ('D', 3));
assert_eq!(rest_display(Some("perc"), 0), ('F', 5));
assert_eq!(rest_display(None, 2), ('F', 5));
assert_eq!(rest_display(None, 3), ('E', 4));
}
#[test]
fn rest_display_edge_cases() {
assert_eq!(rest_display(None, usize::MAX), ('E', 4));
assert_eq!(rest_display(Some("no-such-clef"), 0), ('F', 5));
assert_eq!(rest_display(Some(""), 1), ('E', 4));
}
#[test]
fn slur_placement_matches_the_sl_table() {
let cases = [
("SL1", Some(true), false, 0, "below"),
("SL2", Some(false), false, 0, "above"),
("SL3", None, false, 0, "above"),
("SL4", Some(false), true, 0, "above"),
("SL5", Some(true), true, 1, "below"),
("SL6", None, true, 3, "below"),
("SL7", None, true, 2, "above"),
];
for (name, stem, multivoice, slot, expected) in cases {
assert_eq!(slur_placement(stem, multivoice, slot), expected, "{name}");
}
}
#[test]
fn slur_placement_slot_parity_for_huge_slots() {
assert_eq!(slur_placement(Some(true), true, usize::MAX), "below");
assert_eq!(slur_placement(Some(true), false, usize::MAX), "below");
}
#[test]
fn tie_orientation_matches_the_ti_table() {
let cases = [
("TI1", Some(true), false, "under"),
("TI2", Some(false), false, "over"),
("TI3", None, false, "over"),
("TI4", Some(true), true, "over"),
("TI5", Some(false), true, "under"),
("TI6", None, true, "over"),
];
for (name, stem, multivoice, expected) in cases {
assert_eq!(tie_orientation(stem, multivoice), expected, "{name}");
}
}