complish 0.0.1

Core library for project-aware task management with git integration
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct TaskList {
  pub created_at: DateTime<Utc>,
  pub name: String,
  pub task_ids: Vec<u32>,
  pub updated_at: DateTime<Utc>,
}

impl TaskList {
  pub fn new(name: impl Into<String>) -> Self {
    let now = Utc::now();

    Self {
      created_at: now,
      name: name.into(),
      task_ids: Vec::new(),
      updated_at: now,
    }
  }

  pub fn add_task(&mut self, task_id: u32) {
    self.task_ids.push(task_id);
    self.touch();
  }

  pub fn contains_task(&self, task_id: u32) -> bool {
    self.task_ids.contains(&task_id)
  }

  pub fn is_empty(&self) -> bool {
    self.task_ids.is_empty()
  }

  pub fn len(&self) -> usize {
    self.task_ids.len()
  }

  pub fn remove_task(&mut self, task_id: u32) {
    self.task_ids.retain(|id| *id != task_id);
    self.touch();
  }

  pub fn touch(&mut self) {
    self.updated_at = Utc::now();
  }

  #[must_use]
  pub fn with_tasks(mut self, task_ids: Vec<u32>) -> Self {
    self.task_ids = task_ids;
    self
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  mod new {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_creates_a_new_task_list_with_the_given_name() {
      let name = "test list";
      let list = TaskList::new(name);

      assert_eq!(list.name, name);
      assert!(list.created_at.timestamp() > 0);
      assert!(list.updated_at.timestamp() > 0);
      assert!(list.task_ids.is_empty());
    }
  }

  mod add_task {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_adds_a_task_to_the_list() {
      let mut list = TaskList::new("test list");
      let task_id = 1;
      list.add_task(task_id);

      assert_eq!(list.task_ids, vec![task_id]);
    }
  }

  mod contains_task {
    use super::*;

    #[test]
    fn it_returns_true_if_the_list_contains_the_given_task_id() {
      let mut list = TaskList::new("test list");
      let task_id = 1;
      list.add_task(task_id);

      assert!(list.contains_task(task_id));
    }

    #[test]
    fn it_returns_false_if_the_list_does_not_contain_the_given_task_id() {
      let mut list = TaskList::new("test list");
      let task_id = 1;
      list.add_task(task_id);

      assert!(!list.contains_task(2));
    }
  }

  mod is_empty {
    use super::*;

    #[test]
    fn it_returns_true_if_the_list_is_empty() {
      let list = TaskList::new("test list");

      assert!(list.is_empty());
    }

    #[test]
    fn it_returns_false_if_the_list_is_not_empty() {
      let mut list = TaskList::new("test list");
      list.add_task(1);

      assert!(!list.is_empty());
    }
  }

  mod len {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_the_number_of_tasks_in_the_list() {
      let mut list = TaskList::new("test list");
      list.add_task(1);
      list.add_task(2);

      assert_eq!(list.len(), 2);
    }
  }

  mod remove_task {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_removes_a_task_from_the_list() {
      let mut list = TaskList::new("test list");
      let task_id_1 = 1;
      let task_id_2 = 2;
      list.add_task(task_id_1);
      list.add_task(task_id_2);

      assert_eq!(list.task_ids, vec![task_id_1, task_id_2]);

      list.remove_task(task_id_1);

      assert_eq!(list.task_ids, vec![task_id_2]);
    }
  }

  mod touch {
    use pretty_assertions::assert_ne;

    use super::*;

    #[test]
    fn it_updates_the_lists_updated_at_timestamp() {
      let mut list = TaskList::new("test list");
      let old_updated_at = list.updated_at;

      list.updated_at = old_updated_at - chrono::Duration::seconds(1);
      let very_old_time = list.updated_at;

      list.touch();

      assert_ne!(list.updated_at, very_old_time);
      assert!(list.updated_at > very_old_time);
    }
  }

  mod with_tasks {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_creates_a_task_list_with_task_ids() {
      let task_ids = vec![1, 2, 3];
      let list = TaskList::new("test list").with_tasks(task_ids.clone());

      assert_eq!(list.task_ids, task_ids);
    }
  }
}