cueward-adapter-macos 0.5.0

macOS adapter for Cueward with Safari, Notes, Messages, Reminders, Calendar, Screenshot, Clipboard, and OCR integrations.
use super::tests::Fixture;
use super::*;

#[test]
#[ignore = "requires an unlocked desktop and Accessibility/Screen Recording permissions"]
fn wait_finds_native_text_area_despite_unrequested_description_failures() {
    let receiver = super::input_live_tests::Receiver::start(false);
    let snapshot = receiver.snapshot(1);
    let options = query(WaitCondition::ElementExists, "AXTextArea", None, None);
    assert_eq!(
        wait_for_window(&snapshot.input_target, &options)
            .expect("role-only wait")
            .status,
        WaitStatus::Matched
    );
}

fn query(
    condition: WaitCondition,
    role: &str,
    name: Option<&str>,
    identifier: Option<&str>,
) -> WaitOptions {
    WaitOptions {
        condition,
        selector: Some(WaitSelector {
            role: role.into(),
            name: name.map(str::to_owned),
            identifier: identifier.map(str::to_owned),
        }),
        value: None,
        timeout_ms: 4000,
        interval_ms: 100,
    }
}

fn press_named(fixture: &Fixture, name: &str) {
    let tree = fixture.inspect();
    let node = tree
        .accessibility
        .nodes
        .iter()
        .find(|n| n.name == name)
        .expect("named control");
    press(node.target.as_deref().expect("action target")).expect("press fixture control");
}

fn check_async_conditions(fixture: &Fixture, token: &str) {
    let tree = fixture.inspect();
    let progress = tree
        .accessibility
        .nodes
        .iter()
        .find(|n| n.identifier.as_deref() == Some("progress"))
        .expect("progress identifier exposed");
    let mut value = query(
        WaitCondition::ValueEquals,
        "AXTextField",
        None,
        Some("status"),
    );
    value.value = Some("ready".into());
    value.timeout_ms = 200;
    let timeout = wait_for_window(token, &value).expect("bounded wait");
    assert_eq!(timeout.status, WaitStatus::TimedOut);
    press_named(fixture, "Start");
    value.timeout_ms = 4000;
    let matched = wait_for_window(token, &value).expect("wait for delayed value");
    assert_eq!(matched.status, WaitStatus::Matched);
    assert!(matched.matched_ref.is_some());
    let enabled = query(WaitCondition::Enabled, "AXButton", Some("Continue"), None);
    assert_eq!(
        wait_for_window(token, &enabled).expect("enabled").status,
        WaitStatus::Matched
    );
    let absent = query(
        WaitCondition::ElementAbsent,
        &progress.role,
        None,
        Some("progress"),
    );
    assert_eq!(
        wait_for_window(token, &absent)
            .expect("progress disappeared")
            .status,
        WaitStatus::Matched
    );
    let ambiguous = query(WaitCondition::Enabled, "AXButton", None, None);
    assert_eq!(
        wait_for_window(token, &ambiguous)
            .expect("ambiguous")
            .status,
        WaitStatus::Ambiguous
    );
}

#[test]
#[ignore = "requires an unlocked desktop and Accessibility/Screen Recording permissions"]
fn wait_observes_async_conditions_timeout_ambiguity_and_window_lifecycle() {
    let fixture =
        Fixture::launch_source(include_str!("wait_fixture.swift"), "Cueward Wait Fixture");
    let image = fixture.directory.path().join("window.png");
    let shot = snapshot_window(fixture.window_id, false, image.to_str()).expect("snapshot");
    check_async_conditions(&fixture, &shot.input_target);
    press_named(&fixture, "Rename");
    let exists = query(
        WaitCondition::ElementExists,
        "AXButton",
        Some("Continue"),
        None,
    );
    assert_eq!(
        wait_for_window(&shot.input_target, &exists)
            .expect("old identity")
            .status,
        WaitStatus::WindowChanged
    );
    let fresh = snapshot_window(fixture.window_id, false, image.to_str()).expect("fresh snapshot");
    press_named(&fixture, "Close");
    let gone = WaitOptions {
        condition: WaitCondition::WindowGone,
        selector: None,
        value: None,
        timeout_ms: 1000,
        interval_ms: 100,
    };
    assert_eq!(
        wait_for_window(&fresh.input_target, &gone)
            .expect("closed window")
            .status,
        WaitStatus::Matched
    );
}

fn check_observed_names(fixture: &Fixture, token: &str) {
    let tree = fixture.inspect();
    let node = tree
        .accessibility
        .nodes
        .iter()
        .find(|n| n.identifier.as_deref() == Some("long-name-0"))
        .expect("long-name control");
    assert_eq!(node.name, "測".repeat(512));
    let mut request = query(
        WaitCondition::ElementAbsent,
        &node.role,
        Some(&node.name),
        None,
    );
    request.timeout_ms = 300;
    assert_eq!(
        wait_for_window(token, &request)
            .expect("absence check")
            .status,
        WaitStatus::TimedOut,
        "observed name must not falsely prove absence"
    );
    request.condition = WaitCondition::ElementExists;
    assert_eq!(
        wait_for_window(token, &request)
            .expect("presence check")
            .status,
        WaitStatus::Matched
    );
    request.condition = WaitCondition::Enabled;
    assert_eq!(
        wait_for_window(token, &request)
            .expect("colliding names")
            .status,
        WaitStatus::Ambiguous
    );
    request.selector.as_mut().expect("selector").identifier = node.identifier.clone();
    assert_eq!(
        wait_for_window(token, &request)
            .expect("identifier narrows name")
            .status,
        WaitStatus::Matched
    );
}

fn check_full_values(fixture: &Fixture, token: &str) {
    let tree = fixture.inspect();
    let node = tree
        .accessibility
        .nodes
        .iter()
        .find(|n| n.identifier.as_deref() == Some("long-value"))
        .expect("long-value field");
    assert_eq!(node.value.as_deref(), Some("值".repeat(512).as_str()));
    let mut request = query(
        WaitCondition::ValueEquals,
        &node.role,
        None,
        Some("long-value"),
    );
    request.timeout_ms = 300;
    request.value = node.value.clone();
    assert_eq!(
        wait_for_window(token, &request)
            .expect("prefix is not full value")
            .status,
        WaitStatus::TimedOut
    );
    request.value = Some(format!("{}tail", "值".repeat(512)));
    assert_eq!(
        wait_for_window(token, &request).expect("full value").status,
        WaitStatus::Matched
    );
}

#[test]
#[ignore = "requires an unlocked desktop and Accessibility/Screen Recording permissions"]
fn wait_matches_inspected_long_names_without_truncating_expected_values() {
    let fixture = Fixture::launch_source(
        include_str!("wait_name_fixture.swift"),
        "Cueward Wait Name Fixture",
    );
    let image = fixture.directory.path().join("window.png");
    let shot = snapshot_window(fixture.window_id, false, image.to_str()).expect("snapshot");
    check_observed_names(&fixture, &shot.input_target);
    check_full_values(&fixture, &shot.input_target);
}