rust_widgets 2.8.4

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 180 widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT

use super::*;
use crate::compat::MiniToString;
use crate::event::Event;
#[test]
fn test_shortcut_parsing() {
    // `Ctrl` and `Cmd` both parse to PRIMARY: one declaration, every platform.
    let shortcut = Shortcut::from_string("Ctrl+A").unwrap();
    assert_eq!(shortcut.key, Key::A);
    assert!(shortcut.modifiers.contains(Modifiers::PRIMARY));
    assert!(!shortcut.modifiers.contains(Modifiers::ALT));
    assert_eq!(Shortcut::from_string("Cmd+A").unwrap(), shortcut);
    assert_eq!(Shortcut::from_string("Primary+A").unwrap(), shortcut);
    let shortcut = Shortcut::from_string("Alt+F4").unwrap();
    assert_eq!(shortcut.key, Key::F4);
    assert!(shortcut.modifiers.contains(Modifiers::ALT));
    let shortcut = Shortcut::from_string("Ctrl+Shift+S").unwrap();
    assert_eq!(shortcut.key, Key::S);
    assert!(shortcut.modifiers.contains(Modifiers::PRIMARY));
    assert!(shortcut.modifiers.contains(Modifiers::SHIFT));
    // "Meta"/"Win" stay a distinct, physical modifier.
    let shortcut = Shortcut::from_string("Meta+X").unwrap();
    assert!(shortcut.modifiers.contains(Modifiers::META));
    assert!(!shortcut.modifiers.contains(Modifiers::PRIMARY));
}
#[test]
fn test_shortcut_to_string() {
    // `format_shortcut` is the canonical, platform-independent form, and is an inverse of
    // `from_string`. A *physical* Control shortcut renders as `Control` so it is distinguishable
    // from the `Ctrl` input alias of PRIMARY (N-K-03).
    let shortcut = Shortcut::ctrl(Key::A);
    assert_eq!(shortcut.to_string(), "Control+A");
    let shortcut = Shortcut::new(Key::F1, Modifiers::CTRL | Modifiers::ALT);
    assert_eq!(shortcut.to_string(), "Control+Alt+F1");
    let shortcut = Shortcut::primary(Key::Z);
    assert_eq!(shortcut.to_string(), "Primary+Z");
}

/// Every representable key's canonical spelling must round-trip through
/// `from_string`. The page keys used to format as `PgUp`/`PgDn`, which the parser
/// did not accept, so a bound chord's own canonical spelling could not be parsed back.
#[test]
fn every_key_canonical_spelling_round_trips() {
    use Key::*;
    let keys = [
        None,
        A,
        B,
        C,
        D,
        E,
        F,
        G,
        H,
        I,
        J,
        K,
        L,
        M,
        N,
        O,
        P,
        Q,
        R,
        S,
        T,
        U,
        V,
        W,
        X,
        Y,
        Z,
        Num0,
        Num1,
        Num2,
        Num3,
        Num4,
        Num5,
        Num6,
        Num7,
        Num8,
        Num9,
        F1,
        F2,
        F3,
        F4,
        F5,
        F6,
        F7,
        F8,
        F9,
        F10,
        F11,
        F12,
        Escape,
        Tab,
        Enter,
        Space,
        Backspace,
        Delete,
        Insert,
        Home,
        End,
        PageUp,
        PageDown,
        Left,
        Right,
        Up,
        Down,
        Minus,
        Equals,
        LeftBracket,
        RightBracket,
        Semicolon,
        Quote,
        Comma,
        Period,
        Slash,
        Backslash,
    ];
    for key in keys {
        let spelling = key.format_key();
        assert_eq!(
            Key::from_string(spelling),
            Some(key),
            "canonical spelling {spelling:?} must round-trip",
        );
    }
}

/// Page keys format as `PageUp`/`PageDown` and parse back; the platform display
/// glyphs are display-only and are not accepted as canonical input.
#[test]
fn page_keys_round_trip_without_glyphs() {
    assert_eq!(Key::PageUp.format_key(), "PageUp");
    assert_eq!(Key::from_string("PageUp"), Some(Key::PageUp));
    assert_eq!(Key::PageDown.format_key(), "PageDown");
    assert_eq!(Key::from_string("PageDown"), Some(Key::PageDown));

    // A shortcut using a page key stringifies and parses back to itself.
    let shortcut = Shortcut::primary(Key::PageUp);
    assert_eq!(shortcut.to_string(), "Primary+PageUp");
    assert_eq!(Shortcut::from_string(&shortcut.to_string()), Some(shortcut));

    assert_eq!(Key::from_string("⇞"), None, "the page-up glyph is not canonical input");
    assert_eq!(Key::from_string("⇟"), None, "the page-down glyph is not canonical input");
}

/// N-K-02 / N-K-03: the canonical form round-trips for physical Control, and a second key token is
/// rejected rather than silently dropping the first.
#[test]
fn shortcut_canonical_form_round_trips_and_rejects_multiple_keys() {
    use crate::compat::format;
    use std::collections::HashMap;

    // Physical Control keeps its identity through format -> parse.
    for shortcut in [
        Shortcut::ctrl(Key::Z),
        Shortcut::ctrl_shift(Key::Z),
        Shortcut::ctrl_alt(Key::F1),
        Shortcut::primary(Key::Z),
        Shortcut::new(Key::A, Modifiers::CTRL | Modifiers::META),
    ] {
        let formatted = shortcut.to_string();
        let parsed = Shortcut::from_string(&formatted)
            .unwrap_or_else(|| panic!("{formatted:?} must parse back"));
        assert_eq!(parsed, shortcut, "{formatted:?} did not round-trip to an equal value");
        assert_eq!(
            format!("{parsed:?}"),
            format!("{shortcut:?}"),
            "Eq implies Hash, so Debug must match too"
        );
    }

    // The hash-identity regression is the point: a shortcut must find itself.
    let mut map: HashMap<Shortcut, u32> = HashMap::new();
    map.insert(Shortcut::ctrl(Key::Z), 1);
    let reparsed = Shortcut::from_string(&Shortcut::ctrl(Key::Z).to_string()).unwrap();
    assert_eq!(
        map.get(&reparsed).copied(),
        Some(1),
        "a reparsed shortcut must hash to the same key"
    );

    // A second key token is a malformed declaration, not a silent overwrite.
    assert_eq!(Shortcut::from_string("Ctrl+A+B"), None);
    assert_eq!(Shortcut::from_string("A+B"), None);
    // One key token still parses.
    assert!(Shortcut::from_string("Ctrl+A").is_some());
    assert!(Shortcut::from_string("Control+A").is_some());
    // `Ctrl` (input alias) and `Control` (physical) are distinct values.
    assert_ne!(Shortcut::from_string("Ctrl+A"), Shortcut::from_string("Control+A"));
}
#[test]
fn test_shortcut_manager_register() {
    let mut manager = ShortcutManager::new();
    assert!(manager.register("action1", Shortcut::ctrl(Key::A), "Action 1"));
    assert!(manager.has_action("action1"));
    assert!(manager.has_shortcut(&Shortcut::ctrl(Key::A)));
    assert!(manager.register("action1", Shortcut::ctrl(Key::A), "Action 1"));
    assert!(!manager.register("action2", Shortcut::ctrl(Key::A), "Action 2"));
}
#[test]
fn test_shortcut_manager_trigger() {
    let mut manager = ShortcutManager::new();
    manager.register("action1", Shortcut::ctrl(Key::A), "Action 1");
    assert!(manager.handle_key_event(Key::A, Modifiers::CTRL));
    assert!(!manager.handle_key_event(Key::B, Modifiers::CTRL));
    assert!(!manager.handle_key_event(Key::A, Modifiers::ALT));
}

#[test]
fn test_shortcut_manager_handles_framework_key_press_event() {
    let mut manager = ShortcutManager::new();
    manager.register("save", Shortcut::ctrl(Key::S), "Save");

    assert!(manager.handle_event(&Event::KeyPress { key: 83, modifiers: 2 }));
    assert!(manager.handle_event(&Event::KeyPress { key: 115, modifiers: 2 }));
    assert!(!manager.handle_event(&Event::KeyPress { key: 83, modifiers: 4 }));
    assert!(!manager.handle_event(&Event::KeyRelease { key: 83, modifiers: 2 }));
}

#[test]
fn test_shortcut_key_code_conversion_covers_navigation_keys() {
    assert_eq!(Key::from_key_code(37), Some(Key::Left));
    assert_eq!(Key::from_key_code(39), Some(Key::Right));
    assert_eq!(Key::from_key_code(13), Some(Key::Enter));
    assert_eq!(Key::from_key_code(9999), None);
}
#[test]
fn test_shortcut_manager_enable_disable() {
    let mut manager = ShortcutManager::new();
    manager.register("action1", Shortcut::ctrl(Key::A), "Action 1");
    assert!(manager.handle_key_event(Key::A, Modifiers::CTRL));
    manager.disable("action1");
    assert!(!manager.handle_key_event(Key::A, Modifiers::CTRL));
    manager.enable("action1");
    assert!(manager.handle_key_event(Key::A, Modifiers::CTRL));
}
#[test]
fn test_shortcut_conflict_detection() {
    let shortcut1 = Shortcut::ctrl(Key::A);
    let shortcut2 = Shortcut::ctrl(Key::A);
    let shortcut3 = Shortcut::alt(Key::A);
    assert!(shortcut1.conflicts_with(&shortcut2));
    assert!(!shortcut1.conflicts_with(&shortcut3));
}

/// `PRIMARY` renders as macOS glyphs on Mac and as spelled-out modifiers
/// elsewhere. Both branches are asserted on every host, so a change to either
/// convention is caught without a Windows or macOS machine.
#[test]
fn primary_shortcut_renders_per_platform() {
    use super::PlatformShortcutStyle::{Desktop, Mac};

    let undo = Shortcut::primary(Key::Z);
    assert_eq!(format_shortcut_for_platform(&undo, Mac), "⌘Z");
    assert_eq!(format_shortcut_for_platform(&undo, Desktop), "Ctrl+Z");

    let redo = Shortcut::primary_shift(Key::Z);
    assert_eq!(format_shortcut_for_platform(&redo, Mac), "⇧⌘Z");
    assert_eq!(format_shortcut_for_platform(&redo, Desktop), "Ctrl+Shift+Z");

    let save = Shortcut::primary(Key::S);
    assert_eq!(format_shortcut_for_platform(&save, Mac), "⌘S");
    assert_eq!(format_shortcut_for_platform(&save, Desktop), "Ctrl+S");
}

/// A physical-Control shortcut stays `Ctrl` on desktop and `⌃` on macOS, and is
/// not conflated with `PRIMARY`.
#[test]
fn physical_control_is_not_primary() {
    use super::PlatformShortcutStyle::{Desktop, Mac};

    let control_c = Shortcut::ctrl(Key::C);
    assert_eq!(format_shortcut_for_platform(&control_c, Desktop), "Ctrl+C");
    assert_eq!(format_shortcut_for_platform(&control_c, Mac), "⌃C");

    let primary_c = Shortcut::primary(Key::C);
    assert_ne!(primary_c, control_c);
}

/// macOS menus print arrow/enter keys as glyphs rather than words.
#[test]
fn mac_style_uses_glyphs_for_named_keys() {
    use super::PlatformShortcutStyle::{Desktop, Mac};

    let enter = Shortcut::new(Key::Enter, Modifiers::PRIMARY);
    assert_eq!(format_shortcut_for_platform(&enter, Mac), "⌘↩");
    assert_eq!(format_shortcut_for_platform(&enter, Desktop), "Ctrl+Enter");

    let up = Shortcut::new(Key::Up, Modifiers::ALT);
    assert_eq!(format_shortcut_for_platform(&up, Mac), "⌥↑");
    assert_eq!(format_shortcut_for_platform(&up, Desktop), "Alt+Up");
}

/// The style the library picks by default must match the host it is built for.
#[test]
fn current_style_matches_host() {
    // The compile-target mapping lives in `src/platform/` (principle #36); this
    // test only asserts that `current()` surfaces it faithfully.
    let expected = crate::platform::types::compile_target_shortcut_style();
    assert_eq!(PlatformShortcutStyle::current(), expected);
}

/// A `KeyPress` carrying the Meta/Command bit must match a `Primary` shortcut.
/// This is the link that makes typing Cmd+Z on macOS hit `Shortcut::primary`.
///
/// The Meta bit is the *macOS* primary accelerator. Under the desktop conventions
/// the primary accelerator is Control instead, and the lookup lifts a Control
/// press into `PRIMARY` to reach the same binding. The style is read at runtime
/// (principle #68: no `cfg(target_os)` outside `src/platform/`), so the expected
/// mapping is asserted per style rather than assuming one host's convention.
#[test]
fn command_modifier_bit_matches_primary_shortcut() {
    let mut manager = ShortcutManager::new();
    manager.register("undo", Shortcut::primary(Key::Z), "Undo");

    // Establish each link of the chain explicitly, so a failure names the step.
    assert_eq!(Key::from_key_code(90), Some(Key::Z));
    // Modifier bit 0b1000 is the Meta/Command slot. It lifts into PRIMARY so a
    // Command key event matches `Shortcut::primary`, and it also implies CTRL:
    // a widget that only looks at the control bit (as the editor's own key
    // handling does) still recognises `Cmd+C` as copy.
    let command = Modifiers::from_event_bits(0b1000);
    assert!(command.contains(Modifiers::PRIMARY));
    assert!(command.contains(Modifiers::CTRL));
    assert_eq!(command, Modifiers::PRIMARY | Modifiers::CTRL);

    // Shift+Command must not fire the unshifted command. This holds under both
    // conventions: the extra SHIFT bit survives normalisation either way.
    assert!(!manager.handle_event(&Event::KeyPress { key: 90, modifiers: 0b1001 }));

    match PlatformShortcutStyle::current() {
        // macOS: the Meta/Command bit *is* the primary accelerator.
        PlatformShortcutStyle::Mac => {
            assert!(manager.handle_event(&Event::KeyPress { key: 90, modifiers: 0b1000 }));
        }
        // Windows/Linux: Control is the primary accelerator, so a Control press is
        // lifted into PRIMARY and matches, while a bare Meta press is not this
        // platform's primary accelerator and must not fire the binding.
        PlatformShortcutStyle::Desktop => {
            assert!(manager.handle_event(&Event::KeyPress { key: 90, modifiers: 0b0010 }));
            assert!(!manager.handle_event(&Event::KeyPress { key: 90, modifiers: 0b1000 }));
        }
    }

    // A physical-Control key event always matches a `Shortcut::ctrl` binding.
    let mut control_manager = ShortcutManager::new();
    control_manager.register("physical", Shortcut::ctrl(Key::Z), "Control only");
    assert!(control_manager.handle_event(&Event::KeyPress { key: 90, modifiers: 0b0010 }));
}