typechar 1.0.0

Type any Unicode string on macOS, regardless of keyboard layout
//! End-to-end tests of the compiled binary: exit codes and stdout/stderr wiring.
//! The one path not covered here is actually posting keyboard events — that
//! needs Accessibility permission and a focused window, which CI runners
//! don't have; it stays a manual test (run `typechar €` in a terminal).

use std::process::{Command, Output};

fn typechar(args: &[&str]) -> Output {
    Command::new(env!("CARGO_BIN_EXE_typechar"))
        .args(args)
        .output()
        .expect("failed to run typechar binary")
}

#[test]
fn help_prints_usage_on_stdout_and_exits_zero() {
    let out = typechar(&["--help"]);
    assert_eq!(out.status.code(), Some(0));
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert!(stdout.contains("Usage:"));
    assert!(stdout.contains("--unicode"));
    assert!(out.stderr.is_empty());
}

#[test]
fn version_matches_cargo_toml() {
    let out = typechar(&["--version"]);
    assert_eq!(out.status.code(), Some(0));
    let stdout = String::from_utf8(out.stdout).unwrap();
    assert_eq!(
        stdout.trim(),
        format!("typechar {}", env!("CARGO_PKG_VERSION"))
    );
}

#[test]
fn no_args_is_a_usage_error_on_stderr_with_exit_2() {
    let out = typechar(&[]);
    assert_eq!(out.status.code(), Some(2));
    let stderr = String::from_utf8(out.stderr).unwrap();
    assert!(stderr.contains("nothing to type"));
    assert!(
        stderr.contains("Usage:"),
        "usage errors should reprint the usage"
    );
    assert!(out.stdout.is_empty());
}

#[test]
fn unknown_flag_is_a_usage_error_with_exit_2() {
    let out = typechar(&["--frobnicate"]);
    assert_eq!(out.status.code(), Some(2));
    let stderr = String::from_utf8(out.stderr).unwrap();
    assert!(stderr.contains("unknown option: --frobnicate"));
}

#[test]
fn invalid_codepoint_is_a_usage_error_with_exit_2() {
    let out = typechar(&["--unicode", "zzz"]);
    assert_eq!(out.status.code(), Some(2));
    let stderr = String::from_utf8(out.stderr).unwrap();
    assert!(stderr.contains("invalid hex codepoint: zzz"));
}

#[test]
fn typing_without_permission_fails_with_exit_1_not_2() {
    // In any environment without Accessibility permission (like CI), the
    // typing path must fail loudly with a runtime error (1), never be
    // mistaken for a usage error (2), and never exit 0 without typing.
    // The empty string reaches the permission check but posts zero events,
    // so on a permitted machine (exit 0) nothing lands in the focused window.
    let out = typechar(&[""]);
    let code = out.status.code();
    assert!(
        code == Some(0) || code == Some(1),
        "expected 0 (permitted) or 1 (permission denied), got {code:?}"
    );
    if code == Some(1) {
        let stderr = String::from_utf8(out.stderr).unwrap();
        assert!(
            stderr.contains("Accessibility"),
            "error should tell the user the fix"
        );
    }
}