use crate::terminal::{Terminal, AccessTerminalError, streaming};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Invocation {
ReadLine(Result<String, String>),
Print(String, Result<(), String>),
}
impl Invocation {
pub fn read_line(&self) -> Option<&Result<String, String>> {
match self {
Invocation::ReadLine(v) => Some(v),
Invocation::Print(_, _) => None
}
}
pub fn print(&self) -> Option<(&str, &Result<(), String>)> {
match self {
Invocation::ReadLine(_) => None,
Invocation::Print(out, res) => Some((out, res))
}
}
}
pub trait ReadLineInput {
fn unwrap_input(&self) -> &str;
}
impl ReadLineInput for Option<&Result<String, String>> {
fn unwrap_input(&self) -> &str {
self.unwrap().as_ref().unwrap()
}
}
pub trait PrintOutput {
fn unwrap_output(&self) -> &str;
}
impl PrintOutput for Option<(&str, &Result<(), String>)> {
fn unwrap_output(&self) -> &str {
self.unwrap().0
}
}
pub struct Mock<'d> {
on_read_line: streaming::InputReader<'d>,
on_print: streaming::OutputWriter<'d>,
invocations: Vec<Invocation>,
}
impl<'d> Default for Mock<'d> {
fn default() -> Self {
Self {
on_read_line: Box::new(|| Ok(String::default())),
on_print: Box::new(|_| Ok(())),
invocations: vec![],
}
}
}
impl<'d> Mock<'d> {
#[must_use]
pub fn on_read_line(
mut self,
delegate: impl FnMut() -> Result<String, AccessTerminalError> + 'd,
) -> Self {
self.on_read_line = Box::new(delegate);
self
}
#[must_use]
pub fn on_print(mut self, delegate: impl FnMut(&str) -> Result<(), AccessTerminalError> + 'd) -> Self {
self.on_print = Box::new(delegate);
self
}
pub fn invocations(&self) -> &[Invocation] {
&self.invocations[..]
}
}
impl<'d> Terminal for Mock<'d> {
fn print(&mut self, s: &str) -> Result<(), AccessTerminalError> {
let result = (*self.on_print)(s);
self.invocations.push(Invocation::Print(
s.into(),
result
.as_ref()
.map(Clone::clone)
.map_err(ToString::to_string),
));
result
}
fn read_line(&mut self) -> Result<String, AccessTerminalError> {
let result = (*self.on_read_line)();
self.invocations.push(Invocation::ReadLine(
result
.as_ref()
.map(Clone::clone)
.map_err(ToString::to_string),
));
result
}
}
pub fn lines<S: ToString + 'static>(lines: &[S]) -> impl FnMut() -> Result<String, AccessTerminalError> + '_ {
let mut lines = lines;
move || {
if lines.is_empty() {
return Err(AccessTerminalError("no more lines".into()))
}
let s = &lines[0];
lines = &lines[1..];
Ok(s.to_string())
}
}
#[cfg(test)]
mod tests;