140 lines
4.3 KiB
Rust
140 lines
4.3 KiB
Rust
//! Table tests of the SM-2 scheduler.
|
|
|
|
use notescraft_srs::{EASE_MAX, EASE_MIN, EASE_START, Grade, MAX_INTERVAL_DAYS, State, is_due, new_state, review};
|
|
|
|
const TODAY: u32 = 20_000;
|
|
|
|
#[test]
|
|
fn new_state_is_due_today_with_no_interval() {
|
|
let state = new_state(TODAY);
|
|
assert_eq!(state.ease_x1000, EASE_START);
|
|
assert_eq!(state.interval_days, 0);
|
|
assert_eq!(state.reps, 0);
|
|
assert_eq!(state.lapses, 0);
|
|
assert_eq!(state.due_day, TODAY);
|
|
}
|
|
|
|
#[test]
|
|
fn good_gives_one_day_then_six_then_ease_times_interval() {
|
|
let first = review(new_state(TODAY), Grade::Good, TODAY);
|
|
assert_eq!(first.interval_days, 1);
|
|
assert_eq!(first.reps, 1);
|
|
assert_eq!(first.ease_x1000, EASE_START);
|
|
|
|
let second = review(first, Grade::Good, TODAY);
|
|
assert_eq!(second.interval_days, 6);
|
|
assert_eq!(second.reps, 2);
|
|
|
|
let third = review(second, Grade::Good, TODAY);
|
|
assert_eq!(third.interval_days, 15);
|
|
assert_eq!(third.reps, 3);
|
|
}
|
|
|
|
#[test]
|
|
fn easy_gives_four_then_ten_then_ease_and_bonus() {
|
|
let first = review(new_state(TODAY), Grade::Easy, TODAY);
|
|
assert_eq!(first.interval_days, 4);
|
|
assert_eq!(first.ease_x1000, 2650);
|
|
|
|
let second = review(first, Grade::Easy, TODAY);
|
|
assert_eq!(second.interval_days, 10);
|
|
assert_eq!(second.ease_x1000, 2800);
|
|
|
|
let third = review(second, Grade::Easy, TODAY);
|
|
assert_eq!(third.interval_days, 36);
|
|
assert_eq!(third.ease_x1000, 2950);
|
|
}
|
|
|
|
#[test]
|
|
fn hard_grows_the_interval_and_lowers_the_ease() {
|
|
let first = review(new_state(TODAY), Grade::Hard, TODAY);
|
|
assert_eq!(first.interval_days, 1);
|
|
assert_eq!(first.ease_x1000, 2350);
|
|
|
|
let second = review(first, Grade::Hard, TODAY);
|
|
assert_eq!(second.interval_days, 1);
|
|
assert_eq!(second.ease_x1000, 2200);
|
|
}
|
|
|
|
#[test]
|
|
fn again_resets_the_card_and_lowers_the_ease() {
|
|
let mut state = new_state(TODAY);
|
|
for _ in 0..3 {
|
|
state = review(state, Grade::Good, TODAY);
|
|
}
|
|
assert_eq!(state.interval_days, 15);
|
|
|
|
let state = review(state, Grade::Again, TODAY);
|
|
assert_eq!(state.reps, 0);
|
|
assert_eq!(state.interval_days, 0);
|
|
assert_eq!(state.lapses, 1);
|
|
assert_eq!(state.ease_x1000, 2300);
|
|
assert_eq!(state.due_day, TODAY);
|
|
}
|
|
|
|
#[test]
|
|
fn ease_never_falls_below_the_floor() {
|
|
let mut state = new_state(TODAY);
|
|
for _ in 0..10 {
|
|
state = review(state, Grade::Again, TODAY);
|
|
}
|
|
assert_eq!(state.ease_x1000, EASE_MIN);
|
|
assert_eq!(state.lapses, 10);
|
|
}
|
|
|
|
#[test]
|
|
fn ease_never_rises_above_the_ceiling() {
|
|
let mut state = new_state(TODAY);
|
|
for _ in 0..40 {
|
|
state = review(state, Grade::Easy, TODAY);
|
|
assert!(state.ease_x1000 <= EASE_MAX);
|
|
}
|
|
assert_eq!(state.ease_x1000, EASE_MAX);
|
|
}
|
|
|
|
#[test]
|
|
fn the_interval_is_capped() {
|
|
let state = State { ease_x1000: EASE_MAX, interval_days: MAX_INTERVAL_DAYS, reps: 5, lapses: 0, due_day: TODAY };
|
|
let state = review(state, Grade::Good, TODAY);
|
|
assert_eq!(state.interval_days, MAX_INTERVAL_DAYS);
|
|
|
|
let state = review(state, Grade::Easy, TODAY);
|
|
assert_eq!(state.interval_days, MAX_INTERVAL_DAYS);
|
|
}
|
|
|
|
#[test]
|
|
fn a_day_at_the_end_of_the_range_does_not_overflow() {
|
|
let state = review(new_state(u32::MAX), Grade::Good, u32::MAX);
|
|
assert_eq!(state.due_day, u32::MAX);
|
|
assert!(is_due(&state, u32::MAX));
|
|
}
|
|
|
|
#[test]
|
|
fn is_due_holds_at_the_due_day() {
|
|
let state = review(new_state(TODAY), Grade::Good, TODAY);
|
|
assert_eq!(state.due_day, TODAY + 1);
|
|
assert!(!is_due(&state, TODAY));
|
|
assert!(is_due(&state, TODAY + 1));
|
|
assert!(is_due(&state, TODAY + 2));
|
|
}
|
|
|
|
#[test]
|
|
fn every_grade_over_a_grid_keeps_the_card_sane() {
|
|
let grid = [
|
|
new_state(TODAY),
|
|
State { ease_x1000: EASE_MIN, interval_days: 0, reps: 0, lapses: 3, due_day: TODAY },
|
|
State { ease_x1000: EASE_MAX, interval_days: MAX_INTERVAL_DAYS, reps: 9, lapses: 1, due_day: TODAY },
|
|
];
|
|
let grades = [Grade::Again, Grade::Hard, Grade::Good, Grade::Easy];
|
|
for state in grid {
|
|
for grade in grades {
|
|
let next = review(state, grade, TODAY);
|
|
assert!(next.due_day >= TODAY, "{grade:?} moved the card into the past");
|
|
assert!(next.ease_x1000 >= EASE_MIN);
|
|
assert!(next.ease_x1000 <= EASE_MAX);
|
|
assert!(next.interval_days <= MAX_INTERVAL_DAYS);
|
|
assert_eq!(next.lapses, state.lapses + u32::from(grade == Grade::Again));
|
|
}
|
|
}
|
|
}
|