Skip to content

Commit 16f8eca

Browse files
committed
Add tests
1 parent aea2aca commit 16f8eca

1 file changed

Lines changed: 99 additions & 3 deletions

File tree

app/src/ui/game/quiz.rs

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,13 @@ impl Game {
9090
let mut previous_choices = mem::take(&mut self.choices);
9191
let mut next_choices = self.draw_next_choices();
9292

93-
next_choices.iter_mut().for_each(|ctx| ctx.last_seen = Some(0));
94-
previous_choices.iter_mut().for_each(|ctx| ctx.last_seen = Some(1));
93+
next_choices.sort_by_key(Self::weight);
94+
next_choices
95+
.iter_mut()
96+
.for_each(|ctx| ctx.last_seen = Some(0));
97+
previous_choices
98+
.iter_mut()
99+
.for_each(|ctx| ctx.last_seen = Some(1));
95100
// Everyone left in `pack` or `benched` waited one more round for this
96101
// draw; bump their clock before `benched` graduates into `pack` below.
97102
self.pack
@@ -122,7 +127,10 @@ impl Game {
122127
let shortfall = MULTIPLE_CHOICE_SIZE - self.pack.len();
123128
self.benched.sort_by_key(|ctx| Reverse(Self::weight(ctx)));
124129
let backfill = self.benched.drain(0..shortfall.min(self.benched.len()));
125-
mem::take(&mut self.pack).into_iter().chain(backfill).collect()
130+
mem::take(&mut self.pack)
131+
.into_iter()
132+
.chain(backfill)
133+
.collect()
126134
}
127135
}
128136

@@ -198,3 +206,91 @@ impl BirdContext {
198206
self.consecutively_identified >= LEARN_THRESHOLD
199207
}
200208
}
209+
210+
#[cfg(test)]
211+
mod tests {
212+
use std::collections::HashMap;
213+
214+
use super::*;
215+
216+
fn fake_pack(n: u64) -> Vec<Bird> {
217+
(0..n)
218+
.map(|id| Bird {
219+
id,
220+
common_name: format!("bird-{id}"),
221+
scientific_name: format!("sci-{id}"),
222+
image: String::new(),
223+
sounds: vec![],
224+
})
225+
.collect()
226+
}
227+
228+
#[test]
229+
fn incorrect_answer_resets_streak_but_keeps_mistaken_count() {
230+
let mut game = Game::init(fake_pack(10), false);
231+
232+
game.record_choice(true);
233+
game.record_choice(true);
234+
assert_eq!(game.correct_choice().consecutively_identified, 2);
235+
236+
game.record_choice(false);
237+
assert_eq!(game.correct_choice().consecutively_identified, 0);
238+
assert_eq!(game.correct_choice().mistaken, 1);
239+
// `identified` only counts correct answers, so it's untouched by the miss.
240+
assert_eq!(game.correct_choice().identified, 2);
241+
}
242+
243+
#[test]
244+
fn bird_is_learned_after_learn_threshold_consecutive_corrects() {
245+
let mut game = Game::init(fake_pack(10), false);
246+
247+
for _ in 0..LEARN_THRESHOLD {
248+
assert!(!game.correct_choice().learned());
249+
game.record_choice(true);
250+
}
251+
252+
assert!(game.correct_choice().learned());
253+
}
254+
255+
#[test]
256+
fn is_complete_only_once_every_bird_is_learned() {
257+
let mut game = Game::init(fake_pack(8), false);
258+
assert!(!game.is_complete());
259+
260+
// Each round only advances whichever bird is `correct_choice()`, so
261+
// keep answering correctly and advancing rounds until every bird has
262+
// racked up LEARN_THRESHOLD consecutive corrects.
263+
let mut rounds = 0;
264+
while !game.is_complete() {
265+
game.record_choice(true);
266+
game.set_next_challenge();
267+
rounds += 1;
268+
assert!(rounds < 1000, "did not converge -- possible infinite loop");
269+
}
270+
271+
let (learned, total) = game.progress();
272+
assert_eq!(learned, total);
273+
}
274+
275+
#[test]
276+
fn a_bird_never_reappears_within_one_round_of_being_shown() {
277+
let mut game = Game::init(fake_pack(10), true);
278+
let mut last_shown_round: HashMap<u64, i32> = HashMap::new();
279+
280+
for round in 0..200 {
281+
for ctx in game.choices() {
282+
if let Some(&prev) = last_shown_round.get(&ctx.bird.id) {
283+
assert!(
284+
round - prev >= 2,
285+
"bird {} reappeared after only {} round(s)",
286+
ctx.bird.id,
287+
round - prev
288+
);
289+
}
290+
last_shown_round.insert(ctx.bird.id, round);
291+
}
292+
game.record_choice(true);
293+
game.set_next_challenge();
294+
}
295+
}
296+
}

0 commit comments

Comments
 (0)