nils-macos-agent 0.7.3

CLI crate for nils-macos-agent in the nils-cli workspace.
Documentation
use tempfile::TempDir;

use crate::common;
#[test]
fn wait_sleep_returns_single_result_line() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(cwd.path(), &["wait", "sleep", "--ms", "1"]);
    assert_eq!(out.code, 0, "stderr: {}", out.stderr_text());
    assert_eq!(out.stderr_text(), "");
    assert!(out.stdout_text().starts_with("wait.sleep\t"));
}

#[test]
fn wait_app_active_succeeds_for_terminal_fixture() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(
        cwd.path(),
        &[
            "wait",
            "app-active",
            "--app",
            "Terminal",
            "--timeout-ms",
            "50",
            "--poll-ms",
            "5",
        ],
    );

    assert_eq!(out.code, 0, "stderr: {}", out.stderr_text());
    assert_eq!(out.stderr_text(), "");
}

#[test]
fn wait_app_active_succeeds_for_terminal_bundle_id_fixture() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(
        cwd.path(),
        &[
            "wait",
            "app-active",
            "--bundle-id",
            "com.apple.Terminal",
            "--timeout-ms",
            "50",
            "--poll-ms",
            "5",
        ],
    );

    assert_eq!(out.code, 0, "stderr: {}", out.stderr_text());
    assert_eq!(out.stderr_text(), "");
}

#[test]
fn wait_app_active_timeout_is_runtime_error() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(
        cwd.path(),
        &[
            "wait",
            "app-active",
            "--app",
            "Finder",
            "--timeout-ms",
            "20",
            "--poll-ms",
            "5",
        ],
    );

    assert_eq!(out.code, 1);
    assert_eq!(out.stdout_text(), "");
    assert!(
        out.stderr_text()
            .contains("timed out waiting for app-active")
    );
}

#[test]
fn wait_policy_flags_aliases_work_for_wait_commands() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(
        cwd.path(),
        &[
            "wait",
            "app-active",
            "--app",
            "Terminal",
            "--wait-timeout-ms",
            "60",
            "--wait-poll-ms",
            "5",
        ],
    );

    assert_eq!(out.code, 0, "stderr: {}", out.stderr_text());
    assert_eq!(out.stderr_text(), "");
}

#[test]
fn wait_window_present_succeeds_for_terminal() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(
        cwd.path(),
        &[
            "wait",
            "window-present",
            "--app",
            "Terminal",
            "--window-title-contains",
            "Docs",
            "--timeout-ms",
            "50",
            "--poll-ms",
            "5",
        ],
    );

    assert_eq!(out.code, 0, "stderr: {}", out.stderr_text());
    assert_eq!(out.stderr_text(), "");
}

#[test]
fn wait_window_present_rejects_window_title_contains_without_app() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let out = harness.run(
        cwd.path(),
        &[
            "wait",
            "window-present",
            "--window-title-contains",
            "Docs",
            "--timeout-ms",
            "50",
            "--poll-ms",
            "5",
        ],
    );

    assert_eq!(out.code, 2);
    assert_eq!(out.stdout_text(), "");
    assert!(
        out.stderr_text().contains("requires")
            || out
                .stderr_text()
                .contains("required arguments were not provided")
    );
}

#[test]
fn wait_json_and_tsv_contracts() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");

    let json_out = harness.run(
        cwd.path(),
        &[
            "--format",
            "json",
            "wait",
            "app-active",
            "--app",
            "Terminal",
            "--timeout-ms",
            "50",
            "--poll-ms",
            "5",
        ],
    );
    assert_eq!(json_out.code, 0, "stderr: {}", json_out.stderr_text());
    let payload: serde_json::Value =
        serde_json::from_str(&json_out.stdout_text()).expect("json payload");
    assert_eq!(payload["command"], serde_json::json!("wait.app-active"));

    let tsv_out = harness.run(
        cwd.path(),
        &["--format", "tsv", "wait", "app-active", "--app", "Terminal"],
    );
    assert_eq!(tsv_out.code, 2);
    assert_eq!(tsv_out.stdout_text(), "");
    assert!(
        tsv_out
            .stderr_text()
            .contains("only supported for `windows list` and `apps list`")
    );
}

#[test]
fn wait_ax_present_reports_matched_count_in_json() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");
    let options = harness.cmd_options(cwd.path()).with_env(
        "AGENTS_MACOS_AGENT_AX_LIST_JSON",
        r#"{"nodes":[{"node_id":"1.1","role":"AXButton","enabled":true,"focused":false}],"warnings":[]}"#,
    );

    let out = harness.run_with_options(
        cwd.path(),
        &[
            "--format",
            "json",
            "wait",
            "ax-present",
            "--role",
            "AXButton",
            "--timeout-ms",
            "50",
            "--poll-ms",
            "5",
        ],
        options,
    );

    assert_eq!(out.code, 0, "stderr: {}", out.stderr_text());
    let payload: serde_json::Value =
        serde_json::from_str(&out.stdout_text()).expect("stdout should be json");
    assert_eq!(payload["command"], serde_json::json!("wait.ax-present"));
    assert_eq!(payload["result"]["matched_count"], serde_json::json!(1));
}

#[test]
fn wait_ax_unique_timeout_reports_last_match_count_hint() {
    let harness = common::MacosAgentHarness::new();
    let cwd = TempDir::new().expect("tempdir");
    let options = harness
        .cmd_options(cwd.path())
        .with_env(
            "AGENTS_MACOS_AGENT_AX_LIST_JSON",
            r#"{"nodes":[{"node_id":"1.1","role":"AXButton","enabled":true,"focused":false},{"node_id":"1.2","role":"AXButton","enabled":true,"focused":false}],"warnings":[]}"#,
        );

    let out = harness.run_with_options(
        cwd.path(),
        &[
            "--error-format",
            "json",
            "wait",
            "ax-unique",
            "--role",
            "AXButton",
            "--timeout-ms",
            "20",
            "--poll-ms",
            "5",
        ],
        options,
    );

    assert_eq!(out.code, 1);
    let payload: serde_json::Value =
        serde_json::from_str(&out.stderr_text()).expect("stderr should be json");
    assert_eq!(
        payload["error"]["operation"],
        serde_json::json!("wait.ax-unique")
    );
    let has_hint = payload["error"]["hints"]
        .as_array()
        .map(|hints| {
            hints.iter().any(|hint| {
                hint.as_str()
                    .unwrap_or("")
                    .contains("Last selector match count before timeout: 2")
            })
        })
        .unwrap_or(false);
    assert!(
        has_hint,
        "expected last-match-count hint in wait.ax-unique error"
    );
}