kr580 1.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use super::{DesktopApp, ImportFileFormat, ImportModalFocus};
use crate::app::{MEMORY_SCROLL_VISIBLE_TICKS, Message};
use crate::persistence::{ExportModel, ExportOptions, Exporters};
use std::path::PathBuf;

#[test]
fn import_opens_modal_instead_of_file_dialog_path() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);

    let _task = app.update(Message::Import);

    assert!(app.import_modal_open);
    assert_eq!(app.import_modal_focus, ImportModalFocus::Browse);
    assert!(app.import_file_path.is_none());
    assert!(app.import_target_options.is_empty());
}

#[test]
fn esc_closes_import_modal_without_focus_clear_step() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);

    app.open_import_modal();
    app.import_modal_focus = ImportModalFocus::Target;
    app.import_target_dropdown_open = true;
    let _task = app.update(Message::EscPressed);

    assert!(!app.import_modal_open);
    assert!(!app.import_target_dropdown_open);
}

#[test]
fn import_target_dropdown_reveals_scrollbar_then_hides_until_scrolled() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);

    app.open_import_modal();
    app.import_target_options = vec!["1".to_owned(), "2".to_owned(), "3".to_owned()];
    app.import_target_input = "1".to_owned();
    let _task = app.update(Message::ImportTargetDropdownToggled);

    assert!(app.import_target_dropdown_open);
    assert_eq!(
        app.import_target_scroll_visible_ticks,
        MEMORY_SCROLL_VISIBLE_TICKS
    );
    for _ in 0..MEMORY_SCROLL_VISIBLE_TICKS {
        let _task = app.update(Message::Tick);
    }
    assert_eq!(app.import_target_scroll_visible_ticks, 0);

    let _task = app.update(Message::ImportTargetScrolled);

    assert_eq!(
        app.import_target_scroll_visible_ticks,
        MEMORY_SCROLL_VISIBLE_TICKS
    );
}

#[test]
fn loading_xlsx_import_file_populates_sheet_targets() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);
    let path = unique_temp_file("import-pages.xlsx");
    Exporters::write_xlsx_pages(
        &path,
        &[
            (
                "Подпрограмма 1".to_owned(),
                model_at(0x0100),
                ExportOptions::default(),
            ),
            (
                "Подпрограмма 2".to_owned(),
                model_at(0x0200),
                ExportOptions::default(),
            ),
        ],
    )
    .unwrap();

    app.open_import_modal();
    app.load_import_file(path.clone());

    assert_eq!(app.import_file_path, Some(path.clone()));
    assert_eq!(app.import_file_format, Some(ImportFileFormat::Xlsx));
    assert_eq!(
        app.import_target_options,
        vec!["Подпрограмма 1".to_owned(), "Подпрограмма 2".to_owned()]
    );
    assert_eq!(app.import_target_input, "Подпрограмма 1");
    assert_eq!(app.import_modal_focus, ImportModalFocus::Target);
    assert!(app.import_error.is_none());
    std::fs::remove_file(path).ok();
}

#[test]
fn loading_txt_import_file_populates_section_targets_when_present() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);
    let path = unique_temp_file("import-sections.txt");
    std::fs::write(
        &path,
        Exporters::to_text_sections(&[
            ("Раздел 1".to_owned(), model_at(0x0100)),
            ("Раздел 2".to_owned(), model_at(0x0200)),
        ]),
    )
    .unwrap();

    app.open_import_modal();
    app.load_import_file(path.clone());

    assert_eq!(app.import_file_format, Some(ImportFileFormat::Text));
    assert_eq!(
        app.import_target_options,
        vec!["Раздел 1".to_owned(), "Раздел 2".to_owned()]
    );
    assert_eq!(app.import_target_input, "Раздел 1");
    std::fs::remove_file(path).ok();
}

#[test]
fn confirming_xlsx_import_applies_selected_sheet() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);
    let path = unique_temp_file("confirm-pages.xlsx");
    Exporters::write_xlsx_pages(
        &path,
        &[
            (
                "Подпрограмма 1".to_owned(),
                model_at(0x0100),
                ExportOptions::default(),
            ),
            (
                "Подпрограмма 2".to_owned(),
                model_at(0x0200),
                ExportOptions::default(),
            ),
        ],
    )
    .unwrap();

    app.open_import_modal();
    app.load_import_file(path.clone());
    app.import_target_input = "Подпрограмма 2".to_owned();
    let _task = app.confirm_import();

    assert!(!app.import_modal_open);
    assert_eq!(app.snapshot.cpu.memory.read(0x0100), 0x00);
    assert_eq!(app.snapshot.cpu.memory.read(0x0200), 0xAA);
    std::fs::remove_file(path).ok();
}

#[test]
fn confirming_plain_txt_import_applies_whole_file_without_targets() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);
    let path = unique_temp_file("confirm-plain.txt");
    std::fs::write(&path, Exporters::to_text(&model_at(0x0300))).unwrap();

    app.open_import_modal();
    app.load_import_file(path.clone());
    let _task = app.confirm_import();

    assert!(app.import_target_options.is_empty());
    assert_eq!(app.snapshot.cpu.memory.read(0x0300), 0xAA);
    std::fs::remove_file(path).ok();
}

#[test]
fn confirming_malformed_txt_import_sets_localized_status() {
    let (mut app, _task) = DesktopApp::with_initial_path(None);
    let path = unique_temp_file("malformed-import.txt");
    std::fs::write(&path, "this is not a KR580 export").unwrap();

    app.open_import_modal();
    app.load_import_file(path.clone());
    let _task = app.confirm_import();

    assert_eq!(app.status, "Не удалось прочитать файл – проверьте формат");
    assert!(
        app.error_notice
            .as_deref()
            .is_some_and(|notice| notice.contains(&app.status))
    );
    std::fs::remove_file(path).ok();
}

fn model_at(address: u16) -> ExportModel {
    ExportModel {
        registers: Vec::new(),
        flags: Vec::new(),
        memory: vec![(address, 0xAA)],
    }
}

fn unique_temp_file(name: &str) -> PathBuf {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    std::env::temp_dir().join(format!("{nanos}-{name}"))
}