Files
Marcos Paulo c78fd488cc
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: registry core (model, guard, verify, store, net, install, commands), registry browser UI, signing tool, size profile and font dedupe, lazy Hebrew/Arabic fonts, htmlmd/srs/textdiff crates
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 17:28:10 -03:00

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));
}
}
}