From 107bbf43a8991d28b0418572cc65b37e776d961e Mon Sep 17 00:00:00 2001 From: Mikael Mello Date: Wed, 27 Dec 2023 20:47:21 -0800 Subject: [PATCH] Add several tests for DateSelect --- inquire/Cargo.toml | 3 + inquire/src/prompts/dateselect/mod.rs | 8 +- inquire/src/prompts/dateselect/test.rs | 549 +++++++++++++++++++++++-- inquire/src/ui/backend.rs | 146 +++++++ 4 files changed, 663 insertions(+), 43 deletions(-) diff --git a/inquire/Cargo.toml b/inquire/Cargo.toml index 57c969d4..4bee80d2 100644 --- a/inquire/Cargo.toml +++ b/inquire/Cargo.toml @@ -51,6 +51,9 @@ once_cell = "1.18.0" unicode-segmentation = "1" unicode-width = "0.1" +[dev-dependencies] +chrono = { version = "0.4" } + [[example]] name = "form" required-features = ["date", "macros"] diff --git a/inquire/src/prompts/dateselect/mod.rs b/inquire/src/prompts/dateselect/mod.rs index 1a9e5002..c12c383e 100644 --- a/inquire/src/prompts/dateselect/mod.rs +++ b/inquire/src/prompts/dateselect/mod.rs @@ -15,8 +15,8 @@ use crate::{ error::{InquireError, InquireResult}, formatter::{self, DateFormatter}, prompts::prompt::Prompt, - terminal::{get_default_terminal, Terminal}, - ui::{Backend, RenderConfig}, + terminal::get_default_terminal, + ui::{date::DateSelectBackend, Backend, RenderConfig}, validator::DateValidator, }; @@ -270,9 +270,9 @@ impl<'a> DateSelect<'a> { self.prompt_with_backend(&mut backend) } - pub(crate) fn prompt_with_backend( + pub(crate) fn prompt_with_backend( self, - backend: &mut Backend<'a, T>, + backend: &mut B, ) -> InquireResult { DateSelectPrompt::new(self)?.prompt(backend) } diff --git a/inquire/src/prompts/dateselect/test.rs b/inquire/src/prompts/dateselect/test.rs index 1f0b7aaa..e1c2197a 100644 --- a/inquire/src/prompts/dateselect/test.rs +++ b/inquire/src/prompts/dateselect/test.rs @@ -1,9 +1,14 @@ +use std::vec; + use crate::{ date_utils::get_current_date, - test::fake_backend, - ui::{Key, KeyModifiers}, - validator::Validation, - DateSelect, + error::InquireResult, + ui::{ + test::{FakeBackend, Token}, + Key, KeyModifiers, + }, + validator::{ErrorMessage, Validation}, + DateSelect, InquireError, }; use chrono::NaiveDate; @@ -18,12 +23,14 @@ macro_rules! date_test { ($name:ident,$input:expr,$output:expr,$prompt:expr) => { #[test] - fn $name() { - let mut backend = fake_backend($input); + fn $name() -> InquireResult<()> { + let mut backend = FakeBackend::new($input); - let ans = $prompt.prompt_with_backend(&mut backend).unwrap(); + let ans = $prompt.prompt_with_backend(&mut backend)?; assert_eq!($output, ans); + + Ok(()) } }; } @@ -40,8 +47,8 @@ date_test!( #[test] /// Tests that a closure that actually closes on a variable can be used /// as a DateSelect validator. -fn closure_validator() { - let mut backend = fake_backend(vec![Key::Enter, Key::Left(KeyModifiers::NONE), Key::Enter]); +fn closure_validator() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter, Key::Left(KeyModifiers::NONE), Key::Enter]); let today_date = get_current_date(); @@ -55,15 +62,29 @@ fn closure_validator() { let ans = DateSelect::new("Question") .with_validator(validator) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(today_date.pred_opt().unwrap(), ans); + + let rendered_frames = backend.frames(); + assert!( + rendered_frames[1].has_token(&Token::ErrorMessage(ErrorMessage::Custom( + "Date must be in the past".into() + ))) + ); + assert!(!rendered_frames + .last() + .unwrap() + .has_token(&Token::ErrorMessage(ErrorMessage::Custom( + "Date must be in the past".into() + )))); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn daily_navigation_checks() { +fn daily_navigation_checks() -> InquireResult<()> { let input = vec![ Key::Left(KeyModifiers::NONE), Key::Left(KeyModifiers::NONE), @@ -78,21 +99,22 @@ fn daily_navigation_checks() { Key::Right(KeyModifiers::NONE), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2023, 1, 20).unwrap(), ans); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn weekly_navigation_checks() { +fn weekly_navigation_checks() -> InquireResult<()> { let input = vec![ Key::Up(KeyModifiers::NONE), Key::Up(KeyModifiers::NONE), @@ -107,21 +129,22 @@ fn weekly_navigation_checks() { Key::Down(KeyModifiers::NONE), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2023, 2, 19).unwrap(), ans); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn monthly_navigation_checks() { +fn monthly_navigation_checks() -> InquireResult<()> { let input = vec![ Key::Char('[', KeyModifiers::NONE), Key::Char(']', KeyModifiers::NONE), @@ -131,21 +154,22 @@ fn monthly_navigation_checks() { Key::Char('[', KeyModifiers::NONE), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2022, 11, 15).unwrap(), ans); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn yearly_navigation_checks() { +fn yearly_navigation_checks() -> InquireResult<()> { let input = vec![ Key::Char('}', KeyModifiers::NONE), Key::Char('{', KeyModifiers::NONE), @@ -155,21 +179,22 @@ fn yearly_navigation_checks() { Key::Char('}', KeyModifiers::NONE), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2025, 1, 15).unwrap(), ans); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn naive_navigation_combination() { +fn naive_navigation_combination() -> InquireResult<()> { let input = vec![ // start: 2023-01-15 Key::Up(KeyModifiers::NONE), @@ -208,21 +233,22 @@ fn naive_navigation_combination() { Key::Up(KeyModifiers::NONE), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2024, 12, 24).unwrap(), ans); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn emacs_naive_navigation_combination() { +fn emacs_naive_navigation_combination() -> InquireResult<()> { let input = vec![ // start: 2023-01-15 Key::Char('p', KeyModifiers::CONTROL), @@ -261,21 +287,22 @@ fn emacs_naive_navigation_combination() { Key::Char('p', KeyModifiers::CONTROL), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2024, 12, 24).unwrap(), ans); + + Ok(()) } #[test] /// Tests the behaviour of several keybindings in an admittedly naive way. -fn vim_naive_navigation_combination() { +fn vim_naive_navigation_combination() -> InquireResult<()> { let input = vec![ // start: 2023-01-15 Key::Char('k', KeyModifiers::NONE), @@ -314,14 +341,458 @@ fn vim_naive_navigation_combination() { Key::Char('k', KeyModifiers::NONE), Key::Enter, ]; - let mut backend = fake_backend(input); + let mut backend = FakeBackend::new(input); let starting_date = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap(); let ans = DateSelect::new("Question") .with_starting_date(starting_date) - .prompt_with_backend(&mut backend) - .unwrap(); + .prompt_with_backend(&mut backend)?; assert_eq!(NaiveDate::from_ymd_opt(2024, 12, 24).unwrap(), ans); + + Ok(()) +} + +#[test] +fn default_help_message_exists_and_is_rendered() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let _ = DateSelect::new("Question").prompt_with_backend(&mut backend)?; + + let rendered_frames = backend.frames(); + + for (idx, frame) in rendered_frames.iter().enumerate() { + let is_last_frame = idx == rendered_frames.len() - 1; + + if is_last_frame { + assert!( + frame + .tokens() + .iter() + .all(|t| !matches!(t, Token::HelpMessage(_))), + "Frame {} (last) contained a help message token when it should not have", + idx + ); + } else { + assert!( + frame.has_token(&Token::HelpMessage( + DateSelect::DEFAULT_HELP_MESSAGE.unwrap().into() + )), + "Frame {} did not contain a help message token", + idx + ); + } + } + + Ok(()) +} + +#[test] +fn custom_help_message_is_rendered() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let _ = DateSelect::new("Question") + .with_help_message("Custom help message") + .prompt_with_backend(&mut backend)?; + + let rendered_frames = backend.frames(); + + for (idx, frame) in rendered_frames.iter().enumerate() { + let is_last_frame = idx == rendered_frames.len() - 1; + + if is_last_frame { + assert!( + frame + .tokens() + .iter() + .all(|t| !matches!(t, Token::HelpMessage(_))), + "Frame {} (last) contained a help message token when it should not have", + idx + ); + } else { + assert!( + frame.has_token(&Token::HelpMessage("Custom help message".into())), + "Frame {} did not contain a help message token", + idx + ); + } + } + + Ok(()) +} + +#[test] +fn removing_help_message_results_in_no_help_message_rendered() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let _ = DateSelect::new("Question") + .without_help_message() + .prompt_with_backend(&mut backend)?; + + let rendered_frames = backend.frames(); + + for (idx, frame) in rendered_frames.iter().enumerate() { + assert!( + frame + .tokens() + .iter() + .all(|t| !matches!(t, Token::HelpMessage(_))), + "Frame {} contained a help message token", + idx + ); + } + + Ok(()) +} + +#[test] +fn backend_receives_correct_default_week_start() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let _ = DateSelect::new("Question").prompt_with_backend(&mut backend)?; + + let rendered_frames = backend.frames(); + + assert_eq!( + 2, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + assert!( + rendered_frames[0].tokens().iter().any(|t| matches!( + t, + Token::Calendar { + week_start: DateSelect::DEFAULT_WEEK_START, + .. + } + )), + "Rendered frame did not contain a calendar token with the correct default week start", + ); + + Ok(()) +} + +#[test] +fn backend_receives_correct_custom_week_start() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let _ = DateSelect::new("Question") + .with_week_start(chrono::Weekday::Wed) + .prompt_with_backend(&mut backend)?; + + let rendered_frames = backend.frames(); + + assert_eq!( + 2, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + assert!( + rendered_frames[0].tokens().iter().any(|t| matches!( + t, + Token::Calendar { + week_start: chrono::Weekday::Wed, + .. + } + )), + "Rendered frame did not contain a calendar token with the correct custom week start", + ); + + Ok(()) +} + +#[test] +fn set_min_date_is_respected() -> InquireResult<()> { + let mut moves = vec![Key::Left(KeyModifiers::NONE); 200]; + moves.push(Key::Enter); + let mut backend = FakeBackend::new(moves); + + let custom_min_date = NaiveDate::from_ymd_opt(2022, 12, 25).unwrap(); + let answer = DateSelect::new("Question") + .with_starting_date(NaiveDate::from_ymd_opt(2023, 1, 1).unwrap()) + .with_min_date(custom_min_date) + .prompt_with_backend(&mut backend)?; + + assert_eq!( + custom_min_date, answer, + "Answer was not the expected custom min date" + ); + + let rendered_frames = backend.frames(); + + assert_eq!( + 202, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + for (idx, frame) in rendered_frames[0..201].iter().enumerate() { + assert!(frame.tokens().iter().any( + |t| matches!(t, Token::Calendar { min_date, .. } if *min_date == Some(custom_min_date)) + ), + "Frame {} did not contain a calendar token with the correct min date", idx); + } + + Ok(()) +} + +#[test] +fn set_max_date_is_respected() -> InquireResult<()> { + let mut moves = vec![Key::Right(KeyModifiers::NONE); 200]; + moves.push(Key::Enter); + let mut backend = FakeBackend::new(moves); + + let custom_max_date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(); + let answer = DateSelect::new("Question") + .with_starting_date(NaiveDate::from_ymd_opt(2023, 12, 25).unwrap()) + .with_max_date(custom_max_date) + .prompt_with_backend(&mut backend)?; + + assert_eq!( + custom_max_date, answer, + "Answer was not the expected custom max date" + ); + + let rendered_frames = backend.frames(); + + assert_eq!( + 202, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + for (idx, frame) in rendered_frames[0..201].iter().enumerate() { + assert!(frame.tokens().iter().any( + |t| matches!(t, Token::Calendar { max_date, .. } if *max_date == Some(custom_max_date)) + ), + "Frame {} did not contain a calendar token with the correct max date", idx); + } + + Ok(()) +} + +#[test] +fn no_min_date_means_you_can_go_very_far() -> InquireResult<()> { + let mut moves = vec![Key::Char('{', KeyModifiers::NONE); 2000]; // 2000 years back! + moves.push(Key::Enter); + let mut backend = FakeBackend::new(moves); + + let answer = DateSelect::new("Question") + .with_starting_date(NaiveDate::from_ymd_opt(2023, 1, 1).unwrap()) + .prompt_with_backend(&mut backend)?; + + assert_eq!( + NaiveDate::from_ymd_opt(23, 1, 1).unwrap(), + answer, + "Answer was not the expected custom min date" + ); + + let rendered_frames = backend.frames(); + + assert_eq!( + 2002, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + for (idx, frame) in rendered_frames[0..2001].iter().enumerate() { + assert!( + frame + .tokens() + .iter() + .any(|t| matches!(t, Token::Calendar { min_date: None, .. })), + "Frame {} did not contain a calendar token with None as min date", + idx + ); + } + + Ok(()) +} + +#[test] +fn no_max_date_means_you_can_go_very_far() -> InquireResult<()> { + let mut moves = vec![Key::Char('}', KeyModifiers::NONE); 2000]; // 2000 years forward! + moves.push(Key::Enter); + let mut backend = FakeBackend::new(moves); + + let answer = DateSelect::new("Question") + .with_starting_date(NaiveDate::from_ymd_opt(2023, 1, 1).unwrap()) + .prompt_with_backend(&mut backend)?; + + assert_eq!( + NaiveDate::from_ymd_opt(4023, 1, 1).unwrap(), + answer, + "Answer was not the expected custom min date" + ); + + let rendered_frames = backend.frames(); + + assert_eq!( + 2002, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + for (idx, frame) in rendered_frames[0..2001].iter().enumerate() { + assert!( + frame + .tokens() + .iter() + .any(|t| matches!(t, Token::Calendar { max_date: None, .. })), + "Frame {} did not contain a calendar token with None as max date", + idx + ); + } + + Ok(()) +} + +#[test] +// this test might fail if `today` is set to A and the prompt is initialized +// right after the day turns, becoming A+1, but it's unlikely to happen +fn starting_date_is_today_by_default() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let today = chrono::Local::now().date_naive(); + let prompt = DateSelect::new("Question"); + assert_eq!( + today, prompt.starting_date, + "Starting date configured in prompt was not today" + ); + + let result = prompt.prompt_with_backend(&mut backend)?; + assert_eq!( + today, result, + "Answer selected (starting_date by default) was not today" + ); + + let rendered_frames = backend.frames(); + + assert_eq!( + 2, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + assert!( + rendered_frames[0].tokens().iter().any(|t| matches!( + t, + Token::Calendar { + selected_date, + .. + } if *selected_date == today + )), + "Rendered frame did not contain a calendar token with the correct selected date (today)", + ); + + Ok(()) +} + +#[test] +fn custom_starting_date_is_respected_and_selected_by_default() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let custom_starting_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(); + let prompt = DateSelect::new("Question").with_starting_date(custom_starting_date); + assert_eq!( + custom_starting_date, prompt.starting_date, + "Starting date configured in prompt was not the custom starting date" + ); + + let result = prompt.prompt_with_backend(&mut backend)?; + assert_eq!( + custom_starting_date, result, + "Answer selected (starting_date by default) was not the custom starting date" + ); + + let rendered_frames = backend.frames(); + + assert_eq!( + 2, + rendered_frames.len(), + "Only an initial and final frame should have been rendered", + ); + assert!( + rendered_frames[0].tokens().iter().any(|t| matches!( + t, + Token::Calendar { + selected_date, + .. + } if *selected_date == custom_starting_date + )), + "Rendered frame did not contain a calendar token with the correct selected date (custom starting date)", + ); + + Ok(()) +} + +#[test] +fn custom_formatter_affects_final_output() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let starting_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(); + let custom_formatter = |d: NaiveDate| d.format("WOW! %Y hmm %m xd %d").to_string(); + let result = DateSelect::new("Question") + .with_starting_date(starting_date) + .with_formatter(&custom_formatter) + .prompt_with_backend(&mut backend)?; + + assert_eq!( + starting_date, result, + "Answer selected (starting_date by default) was not the custom starting date" + ); + + let final_frame = backend.frames().last().unwrap(); + + assert!( + final_frame.has_token(&Token::AnsweredPrompt( + "Question".into(), + "WOW! 2023 hmm 01 xd 01".into() + )), + "Final frame did not contain the correct answer token" + ); + + Ok(()) +} + +#[test] +fn default_formatter_outputs_answer_as_extensive_locale() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Enter]); + + let starting_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(); + let expected_output = starting_date.format("%B %-e, %Y").to_string(); + let result = DateSelect::new("Question") + .with_starting_date(starting_date) + .prompt_with_backend(&mut backend)?; + + assert_eq!( + starting_date, result, + "Answer selected (starting_date by default) was not the custom starting date" + ); + + let final_frame = backend.frames().last().unwrap(); + + assert!( + final_frame.has_token(&Token::AnsweredPrompt("Question".into(), expected_output)), + "Final frame did not contain the correct answer token" + ); + + Ok(()) +} + +#[test] +fn escape_raises_error() -> InquireResult<()> { + let mut backend = FakeBackend::new(vec![Key::Escape]); + + let result = DateSelect::new("Question").prompt_with_backend(&mut backend); + + assert!(result.is_err(), "Result was not an error"); + assert!( + matches!(result.unwrap_err(), InquireError::OperationCanceled), + "Error message was not the expected one" + ); + + let final_frame = backend.frames().last().unwrap(); + assert!( + final_frame.has_token(&Token::CanceledPrompt("Question".into())), + "Final frame did not contain the correct canceled prompt token" + ); + + Ok(()) } diff --git a/inquire/src/ui/backend.rs b/inquire/src/ui/backend.rs index 6506ebf9..b6c359b3 100644 --- a/inquire/src/ui/backend.rs +++ b/inquire/src/ui/backend.rs @@ -796,3 +796,149 @@ where Ok(action) } } + +#[cfg(test)] +pub(crate) mod test { + use std::collections::VecDeque; + + use chrono::{Month, NaiveDate, Weekday}; + + use crate::{ui::Key, validator::ErrorMessage}; + + use super::{date::DateSelectBackend, CommonBackend}; + + #[derive(Debug, Clone, PartialEq)] + pub enum Token { + Prompt(String), + CanceledPrompt(String), + AnsweredPrompt(String, String), + ErrorMessage(ErrorMessage), + HelpMessage(String), + Calendar { + month: Month, + year: i32, + week_start: Weekday, + today: NaiveDate, + selected_date: NaiveDate, + min_date: Option, + max_date: Option, + }, + } + + #[derive(Default, Debug, Clone)] + pub struct Frame { + content: Vec, + } + + impl Frame { + pub fn has_token(&self, token: &Token) -> bool { + self.content.iter().any(|t| t == token) + } + + pub fn tokens(&self) -> &[Token] { + &self.content + } + } + + #[derive(Default, Debug, Clone)] + pub struct FakeBackend { + pub input: VecDeque, + pub frames: Vec, + pub cur_frame: Option, + } + + impl FakeBackend { + pub fn new(input: Vec) -> Self { + Self { + input: input.into(), + frames: vec![], + cur_frame: None, + } + } + + fn push_token(&mut self, token: Token) { + if let Some(frame) = self.cur_frame.as_mut() { + frame.content.push(token); + } else { + panic!("No frame to push token"); + } + } + pub fn frames(&self) -> &[Frame] { + &self.frames + } + } + + impl CommonBackend for FakeBackend { + fn read_key(&mut self) -> std::io::Result { + self.input + .pop_front() + .ok_or(std::io::Error::from(std::io::ErrorKind::UnexpectedEof)) + } + + fn frame_setup(&mut self) -> std::io::Result<()> { + self.cur_frame = Some(Frame::default()); + Ok(()) + } + + fn frame_finish(&mut self) -> std::io::Result<()> { + if let Some(frame) = self.cur_frame.take() { + self.frames.push(frame); + } else { + panic!("No frame to finish"); + } + Ok(()) + } + + fn render_canceled_prompt(&mut self, prompt: &str) -> std::io::Result<()> { + self.push_token(Token::CanceledPrompt(prompt.to_string())); + Ok(()) + } + + fn render_prompt_with_answer(&mut self, prompt: &str, answer: &str) -> std::io::Result<()> { + self.push_token(Token::AnsweredPrompt( + prompt.to_string(), + answer.to_string(), + )); + Ok(()) + } + + fn render_error_message(&mut self, error: &ErrorMessage) -> std::io::Result<()> { + self.push_token(Token::ErrorMessage(error.clone())); + Ok(()) + } + + fn render_help_message(&mut self, help: &str) -> std::io::Result<()> { + self.push_token(Token::HelpMessage(help.to_string())); + Ok(()) + } + } + + impl DateSelectBackend for FakeBackend { + fn render_calendar_prompt(&mut self, prompt: &str) -> std::io::Result<()> { + self.push_token(Token::Prompt(prompt.to_string())); + Ok(()) + } + + fn render_calendar( + &mut self, + month: Month, + year: i32, + week_start: Weekday, + today: NaiveDate, + selected_date: NaiveDate, + min_date: Option, + max_date: Option, + ) -> std::io::Result<()> { + self.push_token(Token::Calendar { + month, + year, + week_start, + today, + selected_date, + min_date, + max_date, + }); + Ok(()) + } + } +}