kr580 1.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use crate::app::{DesktopApp, MEMORY_INLINE_INPUT_ID, Message, OPCODE_SEARCH_INPUT_ID, StatusKind};
use std::thread;
use std::time::Duration;

fn app_with_clean_startup() -> DesktopApp {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    for _ in 0..4 {
        thread::sleep(Duration::from_millis(5));
        app.pull_events();
    }
    app
}

#[test]
fn opcode_search_navigation_walks_filtered_results_and_wraps() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.toggle_opcode_dropdown(0x1234);
    app.change_opcode_search("MVI".to_owned());

    assert_eq!(app.highlighted_opcode_value(), Some(0x06));

    app.step_opcode_highlight(1);
    assert_eq!(app.highlighted_opcode_value(), Some(0x0E));

    app.step_opcode_highlight(-1);
    assert_eq!(app.highlighted_opcode_value(), Some(0x06));

    app.step_opcode_highlight(-1);
    assert_eq!(app.highlighted_opcode_value(), Some(0x3E));
}

#[test]
fn opcode_search_keyboard_messages_control_highlight() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.toggle_opcode_dropdown(0x1234);
    app.change_opcode_search("MVI".to_owned());

    let _ = app.update(Message::FocusCycle { backward: false });
    assert_eq!(app.highlighted_opcode_value(), Some(0x0E));
    assert_eq!(app.focused_input, Some(OPCODE_SEARCH_INPUT_ID));

    let _ = app.update(Message::FocusCycle { backward: true });
    assert_eq!(app.highlighted_opcode_value(), Some(0x06));

    let _ = app.update(Message::ArrowKey(-1));
    assert_eq!(app.highlighted_opcode_value(), Some(0x0E));

    let _ = app.update(Message::ArrowKey(1));
    assert_eq!(app.highlighted_opcode_value(), Some(0x06));
}

#[test]
fn enter_applies_highlighted_opcode() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.toggle_opcode_dropdown(0x1234);
    app.change_opcode_search("MVI A".to_owned());

    assert_eq!(app.highlighted_opcode_value(), Some(0x3E));

    let _ = app.update(Message::EnterPressed);

    assert_eq!(app.opcode_dropdown_address, None);
    assert_eq!(app.opcode_search_input, "");
    assert_eq!(app.memory_address_input, "1234");
    assert_eq!(app.memory_value_input, "3E");
}

#[test]
fn pasting_hex_bytes_writes_consecutive_memory_cells_immediately() {
    let mut app = app_with_clean_startup();

    app.change_inline_memory_value(0x0100, "3E 41 D3 03 76".to_owned());
    for _ in 0..10 {
        if app.snapshot.cpu.memory.read(0x0100) == 0x3E {
            break;
        }
        thread::sleep(Duration::from_millis(5));
        app.pull_events();
    }

    assert_eq!(
        &app.snapshot.cpu.memory.as_slice()[0x0100..0x0105],
        &[0x3E, 0x41, 0xD3, 0x03, 0x76]
    );
}

#[test]
fn pasted_hex_bytes_replace_existing_inline_value_after_the_caret() {
    let mut app = app_with_clean_startup();
    app.select_opcode(0x0100, 0xA5);

    app.change_inline_memory_value(0x0100, "A53E 41 D3 03 76".to_owned());
    for _ in 0..10 {
        if app.snapshot.cpu.memory.read(0x0100) == 0x3E {
            break;
        }
        thread::sleep(Duration::from_millis(5));
        app.pull_events();
    }

    assert_eq!(
        &app.snapshot.cpu.memory.as_slice()[0x0100..0x0105],
        &[0x3E, 0x41, 0xD3, 0x03, 0x76]
    );
}

#[test]
fn pasted_hex_bytes_replace_existing_inline_value_before_the_caret() {
    let mut app = app_with_clean_startup();
    app.select_opcode(0x0100, 0xA5);

    app.change_inline_memory_value(0x0100, "3E 41 D3 03 76A5".to_owned());
    for _ in 0..10 {
        if app.snapshot.cpu.memory.read(0x0100) == 0x3E {
            break;
        }
        thread::sleep(Duration::from_millis(5));
        app.pull_events();
    }

    assert_eq!(
        &app.snapshot.cpu.memory.as_slice()[0x0100..0x0105],
        &[0x3E, 0x41, 0xD3, 0x03, 0x76]
    );
}

#[test]
fn value_input_uses_address_zero_when_the_address_field_is_empty() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.memory_address_input.clear();
    app.memory_value_input.clear();

    let _ = app.update(Message::MemoryValueChanged("3E".to_owned()));

    assert_eq!(app.memory_address_input, "0000");
    assert_eq!(app.memory_value_input, "3E");
}

#[test]
fn value_input_is_shared_with_selected_memory_row_preview() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.select_opcode(0x0010, 0x22);

    let _ = app.update(Message::MemoryValueChanged("3E".to_owned()));

    assert_eq!(app.memory_value_input, "3E");
    assert_eq!(app.memory_inline_value_input, "3E");
    assert_eq!(app.snapshot.cpu.memory.read(0x0010), 0x22);
}

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

    let _ = app.update(Message::MemoryValueChanged("20".to_owned()));
    let _ = app.update(Message::MemoryValueChanged("201".to_owned()));
    let _ = app.update(Message::MemoryValueChanged("20G".to_owned()));

    assert_eq!(app.memory_value_input, "20");
    assert!(matches!(app.status_kind, StatusKind::Ready));
}

#[test]
fn overlong_inline_memory_value_input_is_ignored_without_status_error() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.select_opcode(0x0100, 0x20);

    let _ = app.update(Message::InlineMemoryValueChanged(0x0100, "201".to_owned()));
    let _ = app.update(Message::InlineMemoryValueChanged(0x0100, "20G".to_owned()));

    assert_eq!(app.memory_inline_value_input, "20");
    assert!(matches!(app.status_kind, StatusKind::Ready));
}

#[test]
fn pasted_bytes_use_selected_memory_cell_without_inline_edit_focus() {
    let mut app = app_with_clean_startup();
    app.select_memory(0x0100);

    let _ = app.update(Message::MemoryBytesPasted(Some("12 15 16".to_owned())));
    for _ in 0..10 {
        if app.snapshot.cpu.memory.read(0x0100) == 0x12 {
            break;
        }
        thread::sleep(Duration::from_millis(5));
        app.pull_events();
    }

    assert_eq!(app.focused_input, None);
    assert_eq!(app.memory_address_input, "0100");
    assert_eq!(app.memory_value_input, "12");
    assert_eq!(app.memory_inline_value_input, "12");
    assert_eq!(
        &app.snapshot.cpu.memory.as_slice()[0x0100..0x0103],
        &[0x12, 0x15, 0x16]
    );
}

#[test]
fn pasted_bytes_use_address_zero_when_the_address_field_is_empty() {
    let mut app = app_with_clean_startup();
    app.memory_address_input.clear();

    let _ = app.update(Message::MemoryValueChanged("3E 41".to_owned()));
    for _ in 0..10 {
        if app.snapshot.cpu.memory.read(0x0000) == 0x3E {
            break;
        }
        thread::sleep(Duration::from_millis(5));
        app.pull_events();
    }

    assert_eq!(app.memory_address_input, "0000");
    assert_eq!(&app.snapshot.cpu.memory.as_slice()[..2], &[0x3E, 0x41]);
}

#[test]
fn invalid_value_does_not_fill_an_empty_address_field() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.memory_address_input.clear();

    let _ = app.update(Message::MemoryValueChanged("GG".to_owned()));

    assert!(app.memory_address_input.is_empty());
}

#[test]
fn invalid_hex_byte_sequence_does_not_change_memory() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.lang = crate::i18n::Lang::Ru;

    app.change_inline_memory_value(0x0100, "3E nope 76".to_owned());

    assert_eq!(
        &app.snapshot.cpu.memory.as_slice()[0x0100..0x0103],
        &[0x00, 0x00, 0x00]
    );
    assert_eq!(
        app.status,
        "Некорректные байты: используйте HEX-пары через пробел"
    );
    assert!(!app.status.contains("nope"));
}

#[test]
fn invalid_single_pasted_token_reports_a_clear_error() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.lang = crate::i18n::Lang::Ru;

    app.change_inline_memory_value(0x0100, "feature".to_owned());

    assert_eq!(
        app.status,
        "Некорректные байты: используйте HEX-пары через пробел"
    );
    assert!(!app.status.contains("feature"));
}

#[test]
fn invalid_short_hex_token_reports_a_clear_error() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.lang = crate::i18n::Lang::Ru;

    app.change_inline_memory_value(0x0100, "GG".to_owned());

    assert_eq!(
        app.status,
        "Некорректные байты: используйте HEX-пары через пробел"
    );
}

#[test]
fn overflowing_hex_byte_sequence_does_not_change_memory() {
    let (mut app, _) = DesktopApp::with_initial_path(None);
    app.lang = crate::i18n::Lang::Ru;

    app.change_inline_memory_value(0xFFFE, "3E 41 76".to_owned());

    assert_eq!(&app.snapshot.cpu.memory.as_slice()[0xFFFE..], &[0x00, 0x00]);
    assert_eq!(app.status, "Последовательность не помещается в ОЗУ");
}

#[test]
fn inline_memory_enter_keeps_replacement_mode_on_next_cell() {
    let mut app = app_with_clean_startup();
    app.select_opcode(0x0010, 0x3E);
    app.enter_inline_memory_replacing(0x0010);

    assert!(app.memory_inline_value_input.is_empty());
    assert_eq!(app.input_placeholder(MEMORY_INLINE_INPUT_ID, "00"), "3E");

    let _ = app.update(Message::ApplyInlineMemoryValue(0x0010));

    assert_eq!(app.memory_address_input, "0011");
    assert!(app.memory_inline_value_input.is_empty());
    assert_eq!(app.input_placeholder(MEMORY_INLINE_INPUT_ID, "00"), "00");
    assert_eq!(app.snapshot.cpu.memory.read(0x0010), 0x3E);
}