use crate::command::{AgendaRange, CalendarView, CardinalCommand, ListTarget, SyncTarget};
use crate::workspace::{View, WorkspaceState};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DomainEffectRequest {
ReadMaildir,
MoveMessage,
SendMail,
ReadCalendar,
WriteEvent,
RunSync(SyncTarget),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandOutcome {
pub state: WorkspaceState,
pub effect: Option<DomainEffectRequest>,
pub status_message: Option<String>,
}
pub fn dispatch_command(state: &WorkspaceState, command: &CardinalCommand) -> CommandOutcome {
let mut next_state = state.clone();
let mut effect = None;
let mut status_message = None;
match command {
CardinalCommand::List(target) => match target {
ListTarget::Inboxes | ListTarget::Folders => next_state.set_view(View::Inboxes),
ListTarget::Mail => {
next_state.set_view(View::MailList {
mailbox: "mail".to_owned(),
});
effect = Some(DomainEffectRequest::ReadMaildir);
}
ListTarget::Unread => {
next_state.set_view(View::MailList {
mailbox: "unread".to_owned(),
});
effect = Some(DomainEffectRequest::ReadMaildir);
}
ListTarget::Flagged => {
next_state.set_view(View::MailList {
mailbox: "flagged".to_owned(),
});
effect = Some(DomainEffectRequest::ReadMaildir);
}
ListTarget::Calendars => {
next_state.set_view(View::Calendars);
effect = Some(DomainEffectRequest::ReadCalendar);
}
ListTarget::Invites => {
next_state.set_view(View::MailList {
mailbox: "invites".to_owned(),
});
effect = Some(DomainEffectRequest::ReadMaildir);
}
},
CardinalCommand::Calendar(view) => {
let range = match view {
CalendarView::Today => AgendaRange::Today,
CalendarView::Tomorrow => AgendaRange::Tomorrow,
CalendarView::Week => AgendaRange::Week,
CalendarView::Month => AgendaRange::Default,
};
next_state.set_view(View::Agenda { range });
effect = Some(DomainEffectRequest::ReadCalendar);
}
CardinalCommand::Agenda(range) => {
next_state.set_view(View::Agenda {
range: range.clone(),
});
effect = Some(DomainEffectRequest::ReadCalendar);
}
CardinalCommand::Compose
| CardinalCommand::Reply { .. }
| CardinalCommand::Forward { .. } => {
next_state.set_view(View::Compose);
}
CardinalCommand::Send { .. } => {
effect = Some(DomainEffectRequest::SendMail);
}
CardinalCommand::Search { query } => {
next_state.set_view(View::SearchResults {
query: query.clone(),
});
}
CardinalCommand::Archive
| CardinalCommand::Delete
| CardinalCommand::Spam
| CardinalCommand::Mark(_)
| CardinalCommand::Move { .. }
| CardinalCommand::Undo => {
effect = Some(DomainEffectRequest::MoveMessage);
}
CardinalCommand::Event(_) | CardinalCommand::Invite(_) => {
effect = Some(DomainEffectRequest::WriteEvent);
}
CardinalCommand::Sync(target) => {
effect = Some(DomainEffectRequest::RunSync(target.clone()));
status_message = Some("sync queued".to_owned());
}
CardinalCommand::Open(_)
| CardinalCommand::Help
| CardinalCommand::Bindings
| CardinalCommand::Config
| CardinalCommand::Reload
| CardinalCommand::Quit => {}
}
CommandOutcome {
state: next_state,
effect,
status_message,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::command::{CardinalCommand, EventCommand, InviteCommand, MarkState, Selector};
#[test]
fn dispatches_agenda_command_to_typed_view() {
let state = WorkspaceState::default();
let outcome = dispatch_command(&state, &CardinalCommand::Agenda(AgendaRange::Week));
assert_eq!(
outcome.state.view,
View::Agenda {
range: AgendaRange::Week
}
);
assert_eq!(outcome.effect, Some(DomainEffectRequest::ReadCalendar));
}
#[test]
fn dispatches_sync_command_to_effect() {
let state = WorkspaceState::default();
let outcome = dispatch_command(&state, &CardinalCommand::Sync(SyncTarget::Mail));
assert_eq!(
outcome.effect,
Some(DomainEffectRequest::RunSync(SyncTarget::Mail))
);
assert_eq!(outcome.status_message, Some("sync queued".to_owned()));
}
#[test]
fn dispatches_list_calendars_to_calendar_view() {
let state = WorkspaceState::default();
let outcome = dispatch_command(&state, &CardinalCommand::List(ListTarget::Calendars));
assert_eq!(outcome.state.view, View::Calendars);
assert_eq!(outcome.effect, Some(DomainEffectRequest::ReadCalendar));
}
#[test]
fn dispatches_archive_to_mail_move_effect() {
let state = WorkspaceState::default();
let outcome = dispatch_command(&state, &CardinalCommand::Archive);
assert_eq!(outcome.effect, Some(DomainEffectRequest::MoveMessage));
}
#[test]
fn dispatches_mail_list_targets_to_maildir_read() {
let state = WorkspaceState::default();
let list_mail = dispatch_command(&state, &CardinalCommand::List(ListTarget::Mail));
let list_unread = dispatch_command(&state, &CardinalCommand::List(ListTarget::Unread));
let list_flagged = dispatch_command(&state, &CardinalCommand::List(ListTarget::Flagged));
let list_invites = dispatch_command(&state, &CardinalCommand::List(ListTarget::Invites));
assert_eq!(
list_mail.state.view,
View::MailList {
mailbox: "mail".to_owned()
}
);
assert_eq!(
list_unread.state.view,
View::MailList {
mailbox: "unread".to_owned()
}
);
assert_eq!(
list_flagged.state.view,
View::MailList {
mailbox: "flagged".to_owned()
}
);
assert_eq!(
list_invites.state.view,
View::MailList {
mailbox: "invites".to_owned()
}
);
assert_eq!(list_mail.effect, Some(DomainEffectRequest::ReadMaildir));
assert_eq!(list_unread.effect, Some(DomainEffectRequest::ReadMaildir));
assert_eq!(list_flagged.effect, Some(DomainEffectRequest::ReadMaildir));
assert_eq!(list_invites.effect, Some(DomainEffectRequest::ReadMaildir));
}
#[test]
fn dispatches_non_mail_list_targets_without_maildir_effect() {
let state = WorkspaceState::default();
let list_inboxes = dispatch_command(&state, &CardinalCommand::List(ListTarget::Inboxes));
let list_folders = dispatch_command(&state, &CardinalCommand::List(ListTarget::Folders));
assert_eq!(list_inboxes.state.view, View::Inboxes);
assert_eq!(list_folders.state.view, View::Inboxes);
assert_eq!(list_inboxes.effect, None);
assert_eq!(list_folders.effect, None);
}
#[test]
fn dispatches_calendar_views_to_agenda_ranges() {
let state = WorkspaceState::default();
let today = dispatch_command(&state, &CardinalCommand::Calendar(CalendarView::Today));
let tomorrow = dispatch_command(&state, &CardinalCommand::Calendar(CalendarView::Tomorrow));
let week = dispatch_command(&state, &CardinalCommand::Calendar(CalendarView::Week));
let month = dispatch_command(&state, &CardinalCommand::Calendar(CalendarView::Month));
assert_eq!(
today.state.view,
View::Agenda {
range: AgendaRange::Today
}
);
assert_eq!(
tomorrow.state.view,
View::Agenda {
range: AgendaRange::Tomorrow
}
);
assert_eq!(
week.state.view,
View::Agenda {
range: AgendaRange::Week
}
);
assert_eq!(
month.state.view,
View::Agenda {
range: AgendaRange::Default
}
);
assert_eq!(today.effect, Some(DomainEffectRequest::ReadCalendar));
assert_eq!(tomorrow.effect, Some(DomainEffectRequest::ReadCalendar));
assert_eq!(week.effect, Some(DomainEffectRequest::ReadCalendar));
assert_eq!(month.effect, Some(DomainEffectRequest::ReadCalendar));
}
#[test]
fn dispatches_compose_reply_and_forward_to_compose_view() {
let state = WorkspaceState::default();
let compose = dispatch_command(&state, &CardinalCommand::Compose);
let reply = dispatch_command(&state, &CardinalCommand::Reply { all: true });
let forward = dispatch_command(
&state,
&CardinalCommand::Forward {
recipient: "a@example.com".to_owned(),
},
);
assert_eq!(compose.state.view, View::Compose);
assert_eq!(reply.state.view, View::Compose);
assert_eq!(forward.state.view, View::Compose);
}
#[test]
fn dispatches_send_to_sendmail_effect() {
let state = WorkspaceState::default();
let send = dispatch_command(&state, &CardinalCommand::Send { confirm: false });
let confirm = dispatch_command(&state, &CardinalCommand::Send { confirm: true });
assert_eq!(send.effect, Some(DomainEffectRequest::SendMail));
assert_eq!(confirm.effect, Some(DomainEffectRequest::SendMail));
assert_eq!(send.state, state);
}
#[test]
fn dispatches_search_to_search_results_view() {
let state = WorkspaceState::default();
let outcome = dispatch_command(
&state,
&CardinalCommand::Search {
query: "from:alice".to_owned(),
},
);
assert_eq!(
outcome.state.view,
View::SearchResults {
query: "from:alice".to_owned()
}
);
}
#[test]
fn dispatches_all_mail_mutations_to_move_message_effect() {
let state = WorkspaceState::default();
let delete = dispatch_command(&state, &CardinalCommand::Delete);
let spam = dispatch_command(&state, &CardinalCommand::Spam);
let mark = dispatch_command(&state, &CardinalCommand::Mark(MarkState::Read));
let mv = dispatch_command(
&state,
&CardinalCommand::Move {
target: "archive".to_owned(),
},
);
let undo = dispatch_command(&state, &CardinalCommand::Undo);
assert_eq!(delete.effect, Some(DomainEffectRequest::MoveMessage));
assert_eq!(spam.effect, Some(DomainEffectRequest::MoveMessage));
assert_eq!(mark.effect, Some(DomainEffectRequest::MoveMessage));
assert_eq!(mv.effect, Some(DomainEffectRequest::MoveMessage));
assert_eq!(undo.effect, Some(DomainEffectRequest::MoveMessage));
}
#[test]
fn dispatches_noop_commands_without_side_effects() {
let state = WorkspaceState::default();
let open = dispatch_command(&state, &CardinalCommand::Open(Selector::Index(1)));
let help = dispatch_command(&state, &CardinalCommand::Help);
let bindings = dispatch_command(&state, &CardinalCommand::Bindings);
let config = dispatch_command(&state, &CardinalCommand::Config);
let reload = dispatch_command(&state, &CardinalCommand::Reload);
let quit = dispatch_command(&state, &CardinalCommand::Quit);
assert_eq!(open.effect, None);
assert_eq!(help.effect, None);
assert_eq!(bindings.effect, None);
assert_eq!(config.effect, None);
assert_eq!(reload.effect, None);
assert_eq!(quit.effect, None);
assert_eq!(open.state, state);
assert_eq!(quit.state, state);
}
#[test]
fn dispatches_event_and_invite_to_calendar_write_effect() {
let state = WorkspaceState::default();
let event_outcome = dispatch_command(&state, &CardinalCommand::Event(EventCommand::New));
let invite_outcome =
dispatch_command(&state, &CardinalCommand::Invite(InviteCommand::Accept));
assert_eq!(event_outcome.effect, Some(DomainEffectRequest::WriteEvent));
assert_eq!(invite_outcome.effect, Some(DomainEffectRequest::WriteEvent));
}
}