cueward-adapter-macos 0.5.0

macOS adapter for Cueward with Safari, Notes, Messages, Reminders, Calendar, Screenshot, Clipboard, and OCR integrations.
use super::{
    SafariAiImage, SafariAiImageResult, build_chatgpt_click_send_js,
    build_chatgpt_fill_input_js, build_chatgpt_go_home_js, chatgpt_image_extract_js,
    chatgpt_image_list_js, chatgpt_image_result_is_ready, chatgpt_list_conversations_js,
    chatgpt_response_extract_js, chatgpt_should_return_empty_complete_result,
    parse_chatgpt_image_payload,
};
use std::time::Duration;

#[test]
fn chatgpt_go_home_script_targets_new_chat() {
    let script = build_chatgpt_go_home_js();

    assert!(script.contains("chatgpt.com"));
    assert!(script.contains("window.location.href"));
}

#[test]
fn chatgpt_fill_input_script_targets_prompt_textarea() {
    let script = build_chatgpt_fill_input_js("hello from chatgpt");

    assert!(script.contains("#prompt-textarea"));
    assert!(script.contains("hello from chatgpt"));
    assert!(script.contains("execCommand"));
}

#[test]
fn chatgpt_click_send_script_checks_accessible_labels() {
    let script = build_chatgpt_click_send_js();

    assert!(script.contains("Send prompt"));
    assert!(script.contains("Send message"));
    assert!(script.contains("button.disabled"));
}

#[test]
fn chatgpt_response_extract_script_returns_response_and_conversation_url() {
    let script = chatgpt_response_extract_js();

    assert!(script.contains("article"));
    assert!(script.contains("conversation_url"));
    assert!(script.contains("window.location.href"));
    assert!(script.contains("complete"));
}

#[test]
fn chatgpt_list_script_targets_history_nav_links() {
    let script = chatgpt_list_conversations_js();

    assert!(script.contains("Chat history"));
    assert!(script.contains("nav[role=\"navigation\"]"));
    assert!(script.contains("a[href^=\"/c/\"]"));
    assert!(script.contains("https://chatgpt.com"));
    assert!(script.contains("Recent"));
}

#[test]
fn chatgpt_image_list_script_reads_generated_images() {
    let script = chatgpt_image_list_js();

    assert!(script.contains("chatgpt\\.com\\/backend-api\\/estuary\\/content"));
    assert!(script.contains("conversation_url"));
    assert!(script.contains("img.width < 256"));
}

#[test]
fn chatgpt_image_list_script_includes_loaded_signal() {
    let script = chatgpt_image_list_js();

    assert!(script.contains("img.complete"));
    assert!(script.contains("loaded"));
}

#[test]
fn chatgpt_image_extract_script_targets_url() {
    let script = chatgpt_image_extract_js("https://example.com/img.png");

    assert!(script.contains("https://example.com/img.png"));
    assert!(script.contains("drawImage"));
    assert!(!script.contains("new Set"));
}

#[test]
fn parse_chatgpt_image_payload_reads_images() {
    let payload = r#"{"status":"complete","conversation_url":"https://chatgpt.com/c/test","images":[{"url":"https://chatgpt.com/backend-api/estuary/content?id=file_123"}]}"#;
    let result = parse_chatgpt_image_payload(payload).expect("parse payload");

    assert_eq!(result.status, "complete");
    assert_eq!(
        result.conversation_url.as_deref(),
        Some("https://chatgpt.com/c/test")
    );
    assert_eq!(result.images.len(), 1);
    assert_eq!(
        result.images[0].url,
        "https://chatgpt.com/backend-api/estuary/content?id=file_123"
    );
}

#[test]
fn parse_chatgpt_image_payload_reads_loaded_state() {
    let payload = r#"{"status":"complete","conversation_url":"https://chatgpt.com/c/test","images":[{"url":"https://chatgpt.com/backend-api/estuary/content?id=file_123","loaded":true},{"url":"https://chatgpt.com/backend-api/estuary/content?id=file_456","loaded":false}]}"#;
    let result = parse_chatgpt_image_payload(payload).expect("parse payload");

    assert_eq!(result.images.len(), 2);
    assert!(result.images[0].loaded);
    assert!(!result.images[1].loaded);
}

#[test]
fn chatgpt_image_result_requires_all_images_to_be_ready() {
    let unloaded = SafariAiImageResult {
        provider: "chatgpt".to_string(),
        mode: "image".to_string(),
        status: "complete".to_string(),
        conversation_url: Some("https://chatgpt.com/c/test".to_string()),
        images: vec![SafariAiImage {
            url: "https://chatgpt.com/backend-api/estuary/content?id=file_123".to_string(),
            loaded: false,
        }],
    };
    let mixed = SafariAiImageResult {
        images: vec![
            SafariAiImage {
                url: "https://chatgpt.com/backend-api/estuary/content?id=file_123".to_string(),
                loaded: true,
            },
            SafariAiImage {
                url: "https://chatgpt.com/backend-api/estuary/content?id=file_456".to_string(),
                loaded: false,
            },
        ],
        ..unloaded.clone()
    };
    let loaded = SafariAiImageResult {
        images: vec![
            SafariAiImage {
                url: "https://chatgpt.com/backend-api/estuary/content?id=file_123".to_string(),
                loaded: true,
            },
            SafariAiImage {
                url: "https://chatgpt.com/backend-api/estuary/content?id=file_456".to_string(),
                loaded: true,
            },
        ],
        ..unloaded.clone()
    };

    assert!(!chatgpt_image_result_is_ready(&unloaded));
    assert!(!chatgpt_image_result_is_ready(&mixed));
    assert!(chatgpt_image_result_is_ready(&loaded));
}

#[test]
fn chatgpt_empty_complete_grace_only_applies_without_images() {
    let complete_without_images = SafariAiImageResult {
        provider: "chatgpt".to_string(),
        mode: "image".to_string(),
        status: "complete".to_string(),
        conversation_url: Some("https://chatgpt.com/c/test".to_string()),
        images: Vec::new(),
    };
    let complete_with_unloaded_images = SafariAiImageResult {
        images: vec![SafariAiImage {
            url: "https://chatgpt.com/backend-api/estuary/content?id=file_123".to_string(),
            loaded: false,
        }],
        ..complete_without_images.clone()
    };

    assert!(chatgpt_should_return_empty_complete_result(
        &complete_without_images,
        Duration::from_secs(8),
        3
    ));
    assert!(!chatgpt_should_return_empty_complete_result(
        &complete_with_unloaded_images,
        Duration::from_secs(8),
        3
    ));
}