actions 0.2.0

Software without side-effects. Redo and Undo. Macro's.
Documentation
mod helpers;
use crate::helpers::counter::*;

use actions::Timeline;

#[derive(Debug)]
enum Perform {
    Action(CounterAction),
    Undo,
    Redo,
}

/// Perform a change (Action, Undo or Redo) and check (after the change was Performed)
/// if the value of the counter is equal to the expected value.
fn change_and_expect(t: &mut Timeline<Counter>, perform: Perform, expected_value: u32) {
    let tmp = t.current_state().0;

    match perform {
        Perform::Action(ref action) => t.apply(action.clone()).unwrap(),
        Perform::Undo => t.undo().unwrap(),
        Perform::Redo => t.redo().unwrap(),
    };
    assert_eq!(t.current_state().0, expected_value, "Expected the counter to have a value of \"{}\" after performing {:?}. (previous value was {})", expected_value, perform, tmp);
}

#[test]
fn timeline_forward() {
    let mut t = Timeline::new(Counter::default());
    let test_values = vec![
        (CounterAction::Increment, 1),
        (CounterAction::Increment, 2),
        (CounterAction::Increment, 3),
        (CounterAction::Increment, 4),
        (CounterAction::Decrement, 3),
        (CounterAction::Decrement, 2),
        (CounterAction::Increment, 3),
        (CounterAction::SetValue(5), 5),
        (CounterAction::SetValue(7), 7),
        (CounterAction::Decrement, 6),
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, Perform::Action(action), expected_value);
    }
}

#[test]
fn timeline_undo() {
    let mut t = Timeline::new(Counter::default());
    let test_values = vec![
        (Perform::Action(CounterAction::Increment), 1),
        (Perform::Action(CounterAction::Increment), 2),
        (Perform::Action(CounterAction::Increment), 3),
        (Perform::Undo, 2),
        (Perform::Undo, 1),
        (Perform::Action(CounterAction::SetValue(5)), 5),
        (Perform::Undo, 1),
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, action, expected_value);
    }
}

#[test]
#[should_panic]
fn timeline_undo_2() {
    let mut t = Timeline::new(Counter::default());
    let test_values = vec![
        (Perform::Action(CounterAction::Increment), 1),
        (Perform::Action(CounterAction::Increment), 2),
        (Perform::Action(CounterAction::Increment), 3),
        (Perform::Undo, 2),
        (Perform::Undo, 1),
        (Perform::Undo, 0),
        // Should fail because there is no action to be undone
        (Perform::Undo, 0),
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, action, expected_value);
    }
}

#[test]
fn timeline_redo() {
    let mut t = Timeline::new(Counter::default());
    let test_values = vec![
        (Perform::Action(CounterAction::Increment), 1),
        (Perform::Action(CounterAction::Increment), 2),
        (Perform::Action(CounterAction::Increment), 3),
        (Perform::Undo, 2),
        (Perform::Undo, 1),
        (Perform::Redo, 2),
        (Perform::Redo, 3),
        (Perform::Action(CounterAction::Increment), 4),
        (Perform::Undo, 3), // If this fails, there is probably an error in the *undo*.
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, action, expected_value);
    }
}

#[test]
fn timeline_undos_remaining() {
    let mut t = Timeline::new(Counter::default());
    let test_values = vec![
        (Perform::Action(CounterAction::Increment), 1),
        (Perform::Action(CounterAction::Increment), 2),
        (Perform::Action(CounterAction::Increment), 3),
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, action, expected_value);
    }

    assert_eq!(t.undos_remaining(), 3);
}

#[test]
fn timeline_redos_remaining() {
    let mut t = Timeline::new(Counter::default());

    let test_values = vec![
        (Perform::Action(CounterAction::Increment), 1),
        (Perform::Action(CounterAction::Increment), 2),
        (Perform::Action(CounterAction::Increment), 3),
        (Perform::Action(CounterAction::SetValue(5)), 5),
        (Perform::Undo, 3),
        (Perform::Undo, 2),
        (Perform::Undo, 1),
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, action, expected_value);
    }

    // After undoing 3 times, there should be 3 redo's available.
    assert_eq!(t.redos_remaining(), 3);
}

#[test]
fn timeline_redos_remaining_2() {
    let mut t = Timeline::new(Counter::default());

    let test_values = vec![
        (Perform::Action(CounterAction::Increment), 1),
        (Perform::Action(CounterAction::Increment), 2),
        (Perform::Action(CounterAction::Increment), 3),
        (Perform::Undo, 2),
        (Perform::Undo, 1),
        // Performing an action while not at the latest action should
        // make the timeline 'discard the future'...
        (Perform::Action(CounterAction::SetValue(5)), 5),
    ];

    for (action, expected_value) in test_values {
        change_and_expect(&mut t, action, expected_value);
    }
    // ... So there should not be any redo's remaining.
    assert_eq!(t.redos_remaining(), 0);
}