kr580 2.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use super::{captured_register_arrow, command_shortcut_message, runtime_event_message};
use crate::app::{DesktopApp, Message, RegisterMove};
use crate::persistence::{ShortcutAction, ShortcutBinding, ShortcutKey, ShortcutSettings};
use iced::keyboard;
use iced::keyboard::key::{Code, Physical};
use iced::{event, keyboard::Modifiers};
use std::mem::discriminant;

fn char_key(value: &str) -> keyboard::Key {
    keyboard::Key::Character(value.into())
}

fn physical(code: Code) -> Physical {
    Physical::Code(code)
}

fn tab_event(modifiers: Modifiers) -> iced::Event {
    iced::Event::Keyboard(keyboard::Event::KeyPressed {
        key: keyboard::Key::Named(keyboard::key::Named::Tab),
        modified_key: keyboard::Key::Named(keyboard::key::Named::Tab),
        physical_key: physical(Code::Tab),
        location: keyboard::Location::Standard,
        modifiers,
        text: None,
        repeat: false,
    })
}

fn assert_message(actual: Option<Message>, expected: Message) {
    let actual = actual.expect("shortcut should resolve");
    assert_eq!(discriminant(&actual), discriminant(&expected));
}

fn assert_focus_cycle(actual: Option<Message>, backward: bool) {
    assert!(matches!(actual, Some(Message::FocusCycle { backward: actual }) if actual == backward));
}

fn default_settings() -> ShortcutSettings {
    ShortcutSettings::default()
}

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

    app.open_export_modal();
    assert_focus_cycle(
        runtime_event_message(
            &app,
            tab_event(Modifiers::default()),
            event::Status::Captured,
            iced::window::Id::unique(),
        ),
        false,
    );
    assert_focus_cycle(
        runtime_event_message(
            &app,
            tab_event(Modifiers::SHIFT),
            event::Status::Captured,
            iced::window::Id::unique(),
        ),
        true,
    );

    app.close_export_modal();
    app.open_import_modal();
    assert_focus_cycle(
        runtime_event_message(
            &app,
            tab_event(Modifiers::default()),
            event::Status::Captured,
            iced::window::Id::unique(),
        ),
        false,
    );
    assert_focus_cycle(
        runtime_event_message(
            &app,
            tab_event(Modifiers::SHIFT),
            event::Status::Captured,
            iced::window::Id::unique(),
        ),
        true,
    );
}

#[test]
fn captured_plain_arrows_are_forwarded_to_register_navigation() {
    for (key, expected) in [
        (keyboard::key::Named::ArrowUp, RegisterMove::Up),
        (keyboard::key::Named::ArrowDown, RegisterMove::Down),
        (keyboard::key::Named::ArrowLeft, RegisterMove::Left),
        (keyboard::key::Named::ArrowRight, RegisterMove::Right),
    ] {
        let message =
            captured_register_arrow(&keyboard::Key::Named(key), keyboard::Modifiers::default());
        assert!(matches!(
            message,
            Some(Message::RegisterArrowKey(actual)) if actual == expected
        ));
    }
}

#[test]
fn modified_captured_arrows_keep_text_input_behavior() {
    let key = keyboard::Key::Named(keyboard::key::Named::ArrowRight);
    assert!(captured_register_arrow(&key, keyboard::Modifiers::SHIFT).is_none());
    assert!(captured_register_arrow(&key, keyboard::Modifiers::CTRL).is_none());
    assert!(captured_register_arrow(&key, keyboard::Modifiers::ALT).is_none());
}

#[test]
fn captured_ctrl_a_keeps_text_input_select_all() {
    for (typed, code) in [("a", Code::KeyA), ("ф", Code::KeyA)] {
        assert!(
            command_shortcut_message(
                &default_settings(),
                &char_key(typed),
                physical(code),
                Modifiers::COMMAND,
                event::Status::Captured,
            )
            .is_none()
        );
    }
}

#[test]
fn captured_ctrl_v_keeps_text_input_paste() {
    for (typed, code) in [("v", Code::KeyV), ("м", Code::KeyV)] {
        assert!(
            command_shortcut_message(
                &default_settings(),
                &char_key(typed),
                physical(code),
                Modifiers::COMMAND,
                event::Status::Captured,
            )
            .is_none()
        );
    }
}

#[test]
fn ignored_ctrl_v_requests_memory_paste() {
    assert_message(
        command_shortcut_message(
            &default_settings(),
            &char_key("м"),
            physical(Code::KeyV),
            Modifiers::COMMAND,
            event::Status::Ignored,
        ),
        Message::PasteMemoryBytesRequested,
    );
}

#[test]
fn ignored_ctrl_a_still_opens_network_adapter() {
    assert_message(
        command_shortcut_message(
            &default_settings(),
            &char_key("ф"),
            physical(Code::KeyA),
            Modifiers::COMMAND,
            event::Status::Ignored,
        ),
        Message::OpenNetwork,
    );
}

#[test]
fn captured_ctrl_s_still_saves_snapshot() {
    assert_message(
        command_shortcut_message(
            &default_settings(),
            &char_key("ы"),
            physical(Code::KeyS),
            Modifiers::COMMAND,
            event::Status::Captured,
        ),
        Message::SaveSnapshot,
    );
}

#[test]
fn ignored_ctrl_v_uses_custom_shortcut_when_assigned() {
    let mut settings = ShortcutSettings::default();
    settings.assign(
        ShortcutAction::OpenMonitor,
        ShortcutBinding::new(true, false, false, ShortcutKey::V),
    );

    assert_message(
        command_shortcut_message(
            &settings,
            &char_key("м"),
            physical(Code::KeyV),
            Modifiers::COMMAND,
            event::Status::Ignored,
        ),
        Message::OpenMonitor,
    );
}