modes:pair:tests: refactor for more functional style tests

Nested iteration in the form presented before was quite bad - while
it's a bit more efficient to iterate once and do all the tests in that
one go, it can be kinda hard to preserve the context in ones head.

This style makes it easier to see the flow of data and what is being
tested.
This commit is contained in:
2026-04-03 22:11:12 +01:00
committed by oreodave
parent d0de81231e
commit ad4057e890

View File

@@ -101,68 +101,105 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
card::{make_decks, Rank}, card::{make_decks, Rank},
modes::tests::test_non_reflexivity, modes::tests::test_footstool_non_reflexivity,
}; };
#[test] #[test]
fn new() { fn new() {
// Two jokers can never be a pair. /*
There are 3 cases for generating a pair:
1) Not a pair
2) An improper pair made up of one joker
3) A proper pair made up of two playing cards.
*/
// TEST: Two jokers can never be a pair.
assert_eq!(Pair::new(Card::make_joker(), Card::make_joker()), None); assert_eq!(Pair::new(Card::make_joker(), Card::make_joker()), None);
// Iterate over all ranks // TEST: Non pair tests.
for r1 in Rank::iter_all() { Rank::iter_all()
// Iterate over all cards in a rank // Generate a mapping (r1, r2) where r1, r2 are ranks and r1 != r2
for c1 in r1.cards() { .flat_map(|r1| {
// Iterate over all ranks that aren't r1 Rank::iter_all()
for r2 in Rank::iter_all().filter(|&r2| r2 != r1) { .filter(move |&r2| r2 != r1)
// TEST: Cannot compose a pair made from two different .map(move |r2| (r1, r2))
// ranked cards. })
let all_cards = r2.cards(); // Flat Map for combinations of (cards of rank r1, cards of rank r2)
let all_pairs = .flat_map(|(r1, r2)| {
all_cards.filter_map(|c2| Pair::new(c1, c2)); r1.cards()
.flat_map(move |c1| r2.cards().map(move |c2| (c1, c2)))
assert_eq!(all_pairs.count(), 0); })
} .for_each(|(c1, c2)| {
// TEST: Two cards of differing rank cannot be a pair
// Iterate over all cards that are of the same rank as r1.
for c2 in r1.cards() {
// TEST: Pairs are composed of two similar rank cards.
let pair = {
let pair = Pair::new(c1, c2); let pair = Pair::new(c1, c2);
assert_ne!(pair, None); assert!(
pair.is_none(),
"Expected cards {c1} and {c2} to never form a pair."
)
});
// TEST: Improper pair tests.
PlayingCard::iter_deck(1)
.map(Card::PlayingCard)
.map(|c1| {
// TEST: Any card with one joker can be made into a valid pair.
let c2 = Card::make_joker();
let pair = Pair::new(c1, c2);
assert!(
pair.is_some(),
"Expected ({c1}, {c2}) to be a valid pair",
);
pair.unwrap() pair.unwrap()
}; })
.for_each(|pair| {
// Test: Pairs of two playing cards are proper
assert!(pair.is_proper());
// TEST: Pairs always sort their cards in strength.
let (b1, b2) = ordered(c1, c2);
assert_eq!(pair.0, b1);
assert_eq!(pair.1, b2);
}
// TEST: Pairs may have one joker.
let pair = {
let p = Pair::new(c1, Card::make_joker());
assert_ne!(p, None);
p.unwrap()
};
// TEST: Pairs with a joker are improper. // TEST: Pairs with a joker are improper.
assert!(pair.is_improper()); assert!(pair.is_improper(), "Expected {pair} to be improper");
// TEST: Improper pairs have a Joker in Pair::0. // TEST: Improper pairs have a Joker in Pair::0.
assert!(matches!(pair.0, Card::Joker(_))); assert!(
assert!(matches!(pair.1, Card::PlayingCard(_))); matches!(pair.0, Card::Joker(_)),
} "Expected {} to be a joker",
} pair.0
);
// TEST: Improper pairs have a playing card in Pair::1.
assert!(
matches!(pair.1, Card::PlayingCard(_)),
"Expected {} to be a playing card",
pair.1
);
});
// TEST: Proper pair tests
PlayingCard::iter_deck(1)
.map(Card::PlayingCard)
// Flat Map every card (c1) into combinations (c1, card of same rank as c1)
.flat_map(|c1| c1.rank().unwrap().cards().map(move |c2| (c1, c2)))
// Map every (c1, c2) into a pair
.map(|(c1, c2)| {
// TEST: Two cards of similar rank make a valid pair.
let pair = {
let pair = Pair::new(c1, c2);
assert!(
pair.is_some(),
"Expected {c1} and {c2} to form a valid pair."
);
pair.unwrap()
};
// TEST: Pairs of two playing cards are proper
assert!(pair.is_proper(), "Expected {pair} to be proper.");
(c1, c2, pair)
})
.for_each(|(c1, c2, pair)| {
// TEST: Pairs always sort their cards in strength.
let (b1, b2) = ordered(c1, c2);
assert_eq!(pair.0, b1, "Expected {} to be {b1}", pair.0);
assert_eq!(pair.1, b2, "Expected {} to be {b2}", pair.1);
});
} }
/** Create an exhaustive set of pairs for one deck. */
fn exhaustive_pairs_deck() -> impl Iterator<Item = Pair> { fn exhaustive_pairs_deck() -> impl Iterator<Item = Pair> {
// A rank has 4 cards. There are a total of 10 proper pairs that can be
// made out of 4 cards. With n jokers, add 4n improper pairs to the
// total count of pairs for a single rank.
make_decks(1).flat_map(|c1| { make_decks(1).flat_map(|c1| {
make_decks(1).filter_map(move |c2| Pair::new(c1, c2)) make_decks(1).filter_map(move |c2| Pair::new(c1, c2))
}) })
@@ -171,10 +208,7 @@ mod tests {
#[test] #[test]
fn ordering() { fn ordering() {
fn expected_ordering_relation(p1: &Pair, p2: &Pair) -> bool { fn expected_ordering_relation(p1: &Pair, p2: &Pair) -> bool {
let pair_ordering = p1.cmp(p2); match (p1.cmp(p2), p1.1.cmp(&p2.1)) {
let high_card_ordering = p1.1.cmp(&p2.1);
match (pair_ordering, high_card_ordering) {
// For any two pairs, we expect the high cards to dictate the // For any two pairs, we expect the high cards to dictate the
// ordering of the pairs - the lower card should be irrelevant. // ordering of the pairs - the lower card should be irrelevant.
(x, y) if x == y => true, (x, y) if x == y => true,
@@ -184,44 +218,43 @@ mod tests {
// exclusively one of the pairs is improper - pairs that are // exclusively one of the pairs is improper - pairs that are
// improper should be sorted less than the proper one. // improper should be sorted less than the proper one.
(Ordering::Less, Ordering::Equal) => { (Ordering::Less, Ordering::Equal) => {
// p1 has a joker, p2 doesn't => p1 < p2 // p1 is improper, p2 is proper => p1 < p2
p1.is_improper() && p2.is_proper() p1.is_improper() && p2.is_proper()
} }
(Ordering::Greater, Ordering::Equal) => { (Ordering::Greater, Ordering::Equal) => {
// p2 has a joker, p1 doesn't => p1 > p2 // p2 is improper, p1 is proper => p1 > p2
p2.is_improper() && p1.is_proper() p2.is_improper() && p1.is_proper()
} }
_ => false, _ => false,
} }
} }
for p1 in exhaustive_pairs_deck() { exhaustive_pairs_deck()
for p2 in exhaustive_pairs_deck() { // Create a flat map of all combinations of two valid pairs.
// TEST: For any two pair we expect them to have the .flat_map(|p1| exhaustive_pairs_deck().map(move |p2| (p1, p2)))
.for_each(|(p1, p2)| {
// TEST: For any two valid pairs we expect them to have the
// `expected_ordering_relation`. // `expected_ordering_relation`.
assert!(expected_ordering_relation(&p1, &p2)); assert!(expected_ordering_relation(&p1, &p2));
} })
}
} }
#[test] #[test]
fn footstool() { fn footstool() {
for p1 in exhaustive_pairs_deck() { exhaustive_pairs_deck()
for p2 in exhaustive_pairs_deck() { .flat_map(|p1| exhaustive_pairs_deck().map(move |p2| (p1, p2)))
let (p1_on_p2, p2_on_p1) = test_non_reflexivity(&p1, &p2); .for_each(|(p1, p2)| {
let (p1_on_p2, p2_on_p1) =
test_footstool_non_reflexivity(&p1, &p2);
let (hc1_on_hc2, hc2_on_hc1) = { let (hc1_on_hc2, hc2_on_hc1) = {
let [high_card_1, high_card_2] = let high_card_1 = Single::new(p1.1).unwrap();
[p1.1, p2.1].map(Single::new).map(|x| { let high_card_2 = Single::new(p2.1).unwrap();
assert!(x.is_some()); test_footstool_non_reflexivity(&high_card_1, &high_card_2)
x.unwrap()
});
test_non_reflexivity(&high_card_1, &high_card_2)
}; };
// TEST: We expect pair footstools to mirror footstool rules of // TEST: We expect pair footstools to mirror footstool rules of
// Singles on the high card. // Singles on the high card.
assert!(p1_on_p2 == hc1_on_hc2 && p2_on_p1 == hc2_on_hc1); assert!(p1_on_p2 == hc1_on_hc2 && p2_on_p1 == hc2_on_hc1);
} });
}
} }
} }