rmcl 0.4.0

A fully featured Minecraft TUI launcher
use std::path::PathBuf;

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use super::*;
use crate::instance::{
    ModLoader,
    import::{ImportSummary, PackFormat},
};
use crate::tests::TEST_LOCK;

fn key(code: KeyCode) -> KeyEvent {
    KeyEvent::new(code, KeyModifiers::NONE)
}

fn summary() -> ImportSummary {
    ImportSummary {
        name: "Test Pack".to_owned(),
        pack_version: "1.0.0".to_owned(),
        game_version: "1.21.1".to_owned(),
        loader: ModLoader::Fabric,
        loader_version: Some("0.16.14".to_owned()),
        mod_count: 2,
        override_count: 1,
        format: PackFormat::Mrpack,
        archive_path: PathBuf::from("test.mrpack"),
        source: None,
    }
}

#[test]
fn stale_import_results_cannot_replace_the_current_request() {
    let state = Arc::new(Mutex::new(ImportWizardState {
        step: ImportStep::Fetching,
        request_id: 2,
        ..ImportWizardState::default()
    }));

    assert!(!update_current_request(&state, 1, |state| {
        state.step = ImportStep::Confirm;
    }));
    assert_eq!(state.lock().unwrap().step, ImportStep::Fetching);
    assert!(update_current_request(&state, 2, |state| {
        state.step = ImportStep::Confirm;
    }));
    assert_eq!(state.lock().unwrap().step, ImportStep::Confirm);
}

#[test]
fn empty_input_is_ignored_and_escape_returns_to_discovery() {
    let _guard = TEST_LOCK.lock().unwrap_or_else(|error| error.into_inner());
    *DISCOVERY_STATE.lock().unwrap() = crate::tui::widgets::content::DiscoveryState::new_modpacks();
    let mut instances = instances::State {
        show_import_popup: true,
        ..instances::State::default()
    };
    *IMPORT_STATE.lock().unwrap() = ImportWizardState {
        step: ImportStep::Input,
        ..ImportWizardState::default()
    };

    handle_key(&key(KeyCode::Enter), &mut instances);
    assert_eq!(IMPORT_STATE.lock().unwrap().step, ImportStep::Input);

    handle_key(&key(KeyCode::Esc), &mut instances);
    assert_eq!(IMPORT_STATE.lock().unwrap().step, ImportStep::Discover);
    assert!(instances.show_import_popup);

    handle_key(&key(KeyCode::Esc), &mut instances);
    assert!(!instances.show_import_popup);
}

#[test]
fn confirm_returns_the_import_summary() {
    let _guard = TEST_LOCK.lock().unwrap_or_else(|error| error.into_inner());
    let mut instances = instances::State {
        show_import_popup: true,
        ..instances::State::default()
    };
    {
        let mut state = IMPORT_STATE.lock().unwrap();
        *state = ImportWizardState {
            step: ImportStep::Confirm,
            summary: Some(summary()),
            ..ImportWizardState::default()
        };
    }
    *IMPORT_RESULT.lock().unwrap() = None;

    handle_key(&key(KeyCode::Enter), &mut instances);
    let result = take_result().expect("import result");
    assert_eq!(result.summary.name, "Test Pack");
    assert!(!instances.show_import_popup);
}

#[test]
fn discovered_modpack_version_skips_content_install_confirmation() {
    let project = crate::net::modrinth::DiscoveryProject {
        id: "pack-id".to_owned(),
        slug: "test-pack".to_owned(),
        title: "Test Pack".to_owned(),
        description: "A pack".to_owned(),
        downloads: 1,
        icon_url: None,
        icon_bytes: None,
    };
    let mut discovery = crate::tui::widgets::content::DiscoveryState::new_modpacks();
    discovery.list.entries.push(
        crate::tui::widgets::content::discovery::provider_project_entry(
            project,
            "modrinth",
            "test-pack".to_owned(),
            None,
        ),
    );
    discovery.list.list_state.select(Some(0));
    let versions = discovery.begin_versions().unwrap();
    crate::tui::widgets::content::DiscoveryState::push_action_result(
        &versions.pending,
        crate::tui::widgets::content::discovery::DiscoveryActionResult::Versions {
            request_id: versions.request_id,
            project_id: versions.project_id,
            result: Ok(vec![VersionInfo {
                id: "version-id".to_owned(),
                project_id: "pack-id".to_owned(),
                name: "1.0".to_owned(),
                version_number: "1.0".to_owned(),
                game_versions: vec!["1.21.1".to_owned()],
                loaders: vec!["fabric".to_owned()],
                version_type: crate::net::modrinth::VersionType::Release,
                dependencies: Vec::new(),
                date_published: String::new(),
                files: Vec::new(),
            }]),
        },
    );
    discovery.drain_pending();
    assert!(discovery.select_minecraft_version());

    let selected = take_discovered_version(&mut discovery).expect("selected version");

    assert_eq!(selected.version.id, "version-id");
    assert!(discovery.version_popup.is_none());
}

#[test]
fn discovered_modpack_becomes_visible_after_its_icon_is_decoded() {
    let _guard = TEST_LOCK.lock().unwrap_or_else(|error| error.into_inner());
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        *DISCOVERY_STATE.lock().unwrap() =
            crate::tui::widgets::content::DiscoveryState::new_modpacks();

        let request = DISCOVERY_STATE.lock().unwrap().begin_modpack_search();
        let mut png = std::io::Cursor::new(Vec::new());
        image::DynamicImage::new_rgba8(1, 1)
            .write_to(&mut png, image::ImageFormat::Png)
            .unwrap();
        let project = crate::net::modrinth::DiscoveryProject {
            id: "pack-id".to_owned(),
            slug: "test-pack".to_owned(),
            title: "Test Pack".to_owned(),
            description: "A pack".to_owned(),
            downloads: 1,
            icon_url: None,
            icon_bytes: Some(png.into_inner()),
        };
        assert!(request.stream.upsert(
            crate::tui::widgets::content::discovery::provider_project_entry(
                project,
                "modrinth",
                "testpack".to_owned(),
                None,
            )
        ));
        crate::tui::widgets::content::DiscoveryState::push_result(
            &request.pending,
            request.generation,
            request.offset,
            Ok(
                crate::tui::widgets::content::discovery::DiscoveryPageResult {
                    received: 1,
                    total_hits: 1,
                },
            ),
        );

        let picker = ratatui_image::picker::Picker::halfblocks();
        tokio::time::timeout(std::time::Duration::from_secs(5), async {
            loop {
                drain(&picker);
                if !DISCOVERY_STATE
                    .lock()
                    .unwrap()
                    .list
                    .filtered_indices()
                    .is_empty()
                {
                    break;
                }
                tokio::task::yield_now().await;
            }
        })
        .await
        .expect("modpack icon render completed");
    });
}