toddi 0.3.2

A TODO focuser built on top of todo.txt
Documentation
use std::{path::PathBuf, process::Command};

use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt};
use assert_fs::prelude::FileWriteStr;
use predicates::prelude::predicate;

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use rexpect::session::spawn_command;

    use super::*;

    const DIR: &str = env!("CARGO_MANIFEST_DIR");

    fn get_config_path() -> String {
        let mut path_buf = PathBuf::from(DIR);
        path_buf.push("tests/config.toml");
        path_buf.to_str().unwrap().to_string()
    }

    fn get_primary_test_todo_path() -> String {
        let mut path_buf = PathBuf::from(DIR);
        path_buf.push("tests/source1/todo.txt");
        path_buf.to_str().unwrap().to_string()
    }

    fn get_secondary_test_todo_path() -> String {
        let mut path_buf = PathBuf::from(DIR);
        path_buf.push("tests/source2/todo.txt");
        path_buf.to_str().unwrap().to_string()
    }

    fn get_tertiary_test_todo_path() -> String {
        let mut path_buf = PathBuf::from(DIR);
        path_buf.push("tests/source3/todo.txt");
        path_buf.to_str().unwrap().to_string()
    }

    #[test]
    fn todo_txt_file_does_not_exist() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;

        cmd.arg("-t")
            .arg("test/file/does/not/exist")
            .arg("-c")
            .arg(get_config_path());
        cmd.assert()
            .failure()
            .stderr(predicate::str::contains("could not read file"));

        Ok(())
    }

    #[test]
    fn task_exists() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_primary_test_todo_path())
            .arg("-c")
            .arg(get_config_path())
            .arg("projects")
            .arg("alpha");
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Project ALPHA"))
            .stdout(predicate::str::contains("todo: <<< match this >>>"));

        Ok(())
    }

    #[test]
    fn tasks_exist() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_primary_test_todo_path())
            .arg("-c")
            .arg(get_config_path())
            .arg("projects")
            .arg("alpha")
            .arg("bravo");
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Project ALPHA"))
            .stdout(predicate::str::contains("todo: <<< match this >>>"))
            .stdout(predicate::str::contains("Project BRAVO"))
            .stdout(predicate::str::contains("todo: <<< match this as well >>>"));

        Ok(())
    }

    #[test]
    fn task_not_found() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_primary_test_todo_path().as_str())
            .arg("-c")
            .arg(get_config_path())
            .arg("projects")
            .arg("zulu");
        cmd.assert().success().stdout(predicate::str::contains(
            "Project `zulu` contains no task as of now",
        ));

        Ok(())
    }

    #[test]
    fn no_project_name_input() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_primary_test_todo_path().as_str())
            .arg("-c")
            .arg(get_config_path());
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Project ALPHA"))
            .stdout(predicate::str::contains("todo: <<< match this >>>"));

        Ok(())
    }

    #[test]
    fn empty_todo_list() -> Result<(), Box<dyn std::error::Error>> {
        let config = assert_fs::NamedTempFile::new("config.toml")?;
        let mut config_str: String = String::from("todo_files = ['");
        config_str.push_str(get_primary_test_todo_path().as_str());
        config_str.push_str("']");
        config.write_str(&config_str)?;

        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_tertiary_test_todo_path())
            .arg("-c")
            .arg(config.path());
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("todo list is empty!"));

        Ok(())
    }

    #[test]
    fn no_partial_project_name_match() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_primary_test_todo_path().as_str())
            .arg("-c")
            .arg(get_config_path())
            .arg("projects")
            .arg("alph");
        cmd.assert().success().stdout(predicate::str::contains(
            "Project `alph` contains no task as of now.",
        ));

        Ok(())
    }

    #[test]
    fn test_configuration_input() -> Result<(), Box<dyn std::error::Error>> {
        let config = assert_fs::NamedTempFile::new("config.toml")?;
        let mut config_str: String = String::from("todo_files = ['");
        config_str.push_str(get_primary_test_todo_path().as_str());
        config_str.push_str("']");
        config.write_str(&config_str)?;

        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-c")
            .arg(config.path())
            .arg("projects")
            .arg("alpha");
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("Project ALPHA"))
            .stdout(predicate::str::contains("match this"));

        Ok(())
    }

    #[test]
    fn test_multiple_todos() -> Result<(), Box<dyn std::error::Error>> {
        let config = assert_fs::NamedTempFile::new("config.toml")?;
        let mut config_str: String = String::from("todo_files = ['");
        config_str.push_str(get_primary_test_todo_path().as_str());
        config_str.push_str("', '");
        config_str.push_str(get_secondary_test_todo_path().as_str());
        config_str.push_str("']");
        config.write_str(&config_str)?;

        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-c")
            .arg(config.path())
            .arg("projects")
            .arg("charlie");
        cmd.assert()
            .success()
            .stdout(predicate::str::contains("match this"))
            .stdout(predicate::str::contains("Project CHARLIE"));

        Ok(())
    }

    #[test]
    fn test_focus_task_quit() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("toddi")?;
        cmd.arg("-t")
            .arg(get_primary_test_todo_path())
            .arg("-c")
            .arg(get_config_path())
            .arg("focus");

        let mut p = spawn_command(cmd, Some(5_000))?;
        p.exp_string("todo: ")?;
        p.exp_string("Is it done? (y/n/q): ")?;
        p.send_line("q")?;
        p.exp_string("Alright, we are done for now.")?;
        p.exp_eof()?;

        Ok(())
    }
}