diff --git a/rslib/src/collection.rs b/rslib/src/collection.rs index dad3d3b5b..7b652a7b8 100644 --- a/rslib/src/collection.rs +++ b/rslib/src/collection.rs @@ -42,7 +42,13 @@ pub fn open_collection>( #[cfg(test)] pub fn open_test_collection() -> Collection { - open_test_collection_with_server(false) + use crate::config::SchedulerVersion; + let col = open_test_collection_with_server(false); + // our unit tests assume v2 is the default, but at the time of writing v1 + // is still the default + col.set_scheduler_version_config_key(SchedulerVersion::V2) + .unwrap(); + col } #[cfg(test)] diff --git a/rslib/src/config.rs b/rslib/src/config.rs index 925dde90c..14ebf3fef 100644 --- a/rslib/src/config.rs +++ b/rslib/src/config.rs @@ -70,7 +70,7 @@ pub(crate) enum ConfigKey { ShowIntervalsAboveAnswerButtons, ShowRemainingDueCountsInStudy, } -#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy)] +#[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy, Debug)] #[repr(u8)] pub(crate) enum SchedulerVersion { V1 = 1, diff --git a/rslib/src/decks/mod.rs b/rslib/src/decks/mod.rs index 1c26e46b0..9c69af06d 100644 --- a/rslib/src/decks/mod.rs +++ b/rslib/src/decks/mod.rs @@ -76,6 +76,35 @@ impl Deck { } } + // used by tests at the moment + + #[allow(dead_code)] + pub(crate) fn normal(&self) -> Result<&NormalDeck> { + if let DeckKind::Normal(normal) = &self.kind { + Ok(normal) + } else { + Err(AnkiError::invalid_input("deck not normal")) + } + } + + #[allow(dead_code)] + pub(crate) fn normal_mut(&mut self) -> Result<&mut NormalDeck> { + if let DeckKind::Normal(normal) = &mut self.kind { + Ok(normal) + } else { + Err(AnkiError::invalid_input("deck not normal")) + } + } + + #[allow(dead_code)] + pub(crate) fn filtered_mut(&mut self) -> Result<&mut FilteredDeck> { + if let DeckKind::Filtered(filtered) = &mut self.kind { + Ok(filtered) + } else { + Err(AnkiError::invalid_input("deck not filtered")) + } + } + pub fn human_name(&self) -> String { self.name.replace("\x1f", "::") } diff --git a/rslib/src/scheduler/answering/preview.rs b/rslib/src/scheduler/answering/preview.rs index 074990213..9bf385477 100644 --- a/rslib/src/scheduler/answering/preview.rs +++ b/rslib/src/scheduler/answering/preview.rs @@ -38,3 +38,89 @@ impl CardStateUpdater { )) } } + +#[cfg(test)] +mod test { + use crate::collection::open_test_collection; + + use super::*; + use crate::{ + card::CardType, + scheduler::{ + answering::{CardAnswer, Rating}, + states::{CardState, FilteredState, LearnState, NormalState}, + }, + timestamp::TimestampMillis, + }; + + #[test] + fn preview() -> Result<()> { + let mut col = open_test_collection(); + dbg!(col.scheduler_version()); + let mut c = Card { + deck_id: DeckID(1), + ctype: CardType::Learn, + queue: CardQueue::Learn, + remaining_steps: 2, + ..Default::default() + }; + col.add_card(&mut c)?; + + // set the first (current) step to a day + let deck = col.storage.get_deck(DeckID(1))?.unwrap(); + let mut conf = col + .get_deck_config(DeckConfID(deck.normal()?.config_id), false)? + .unwrap(); + *conf.inner.learn_steps.get_mut(0).unwrap() = 24.0 * 60.0; + col.add_or_update_deck_config(&mut conf, false)?; + + // pull the card into a preview deck + let mut filtered_deck = Deck::new_filtered(); + filtered_deck.filtered_mut()?.reschedule = false; + col.add_or_update_deck(&mut filtered_deck)?; + assert_eq!(col.rebuild_filtered_deck(filtered_deck.id)?, 1); + + // the original state reflects the learning steps, not the card properties + let next = col.get_next_card_states(c.id)?; + assert_eq!( + next.current, + CardState::Filtered(FilteredState::Preview(PreviewState { + scheduled_secs: 600, + original_state: NormalState::Learning(LearnState { + remaining_steps: 2, + scheduled_secs: 86_400, + }), + })) + ); + + // use Again on the preview + col.answer_card(&CardAnswer { + card_id: c.id, + current_state: next.current, + new_state: next.again, + rating: Rating::Again, + answered_at: TimestampMillis::now(), + milliseconds_taken: 0, + })?; + + c = col.storage.get_card(c.id)?.unwrap(); + assert_eq!(c.queue, CardQueue::PreviewRepeat); + + // and then it should return to its old state once passed + // (based on learning steps) + let next = col.get_next_card_states(c.id)?; + col.answer_card(&CardAnswer { + card_id: c.id, + current_state: next.current, + new_state: next.hard, + rating: Rating::Hard, + answered_at: TimestampMillis::now(), + milliseconds_taken: 0, + })?; + c = col.storage.get_card(c.id)?.unwrap(); + assert_eq!(c.queue, CardQueue::DayLearn); + assert_eq!(c.due, 1); + + Ok(()) + } +}