trnovel 0.18.2

Terminal reader for novel
use super::*;
use ratatui_kit_keymap::crokey::key;

#[test]
fn chapter_search_keys_and_overrides_preserve_case() {
    let mut km = reader_defaults();
    assert_eq!(km.action_for(key!(s)), Some(ReaderAction::SearchContent));
    assert_eq!(km.action_for(key!(n)), Some(ReaderAction::NextSearchMatch));
    assert_eq!(
        km.action_for(key!(shift - n)),
        Some(ReaderAction::PrevSearchMatch)
    );
    assert_eq!(
        km.action_for(key!(esc)),
        Some(ReaderAction::ClearContentSearch)
    );
    assert_eq!(display_first_key(&km, ReaderAction::NextSearchMatch), "n");
    assert_eq!(display_first_key(&km, ReaderAction::PrevSearchMatch), "N");
    let table = ratatui_kit_keymap::toml::from_str(
        "search_content = [\"ctrl-s\"]\nnext_search_match = [\"f3\"]\nprev_search_match = [\"shift-f3\"]\nclear_content_search = [\"ctrl-x\"]"
    ).unwrap();
    assert!(km.merge_toml_table(table).is_empty());
    assert_eq!(km.action_for(key!(s)), None);
    assert_eq!(
        km.action_for(key!(ctrl - s)),
        Some(ReaderAction::SearchContent)
    );
    assert_eq!(km.action_for(key!(f3)), Some(ReaderAction::NextSearchMatch));
    assert_eq!(
        km.action_for(key!(shift - f3)),
        Some(ReaderAction::PrevSearchMatch)
    );
    assert_eq!(
        km.action_for(key!(ctrl - x)),
        Some(ReaderAction::ClearContentSearch)
    );
}

/// 默认表必须能构建(无冲突、键位全部合法)—— build 内有断言,构建成功即验证。
#[test]
fn reader_defaults_build_and_match_legacy_keys() {
    let km = reader_defaults();
    // 逐键抽查与迁移前硬编码一致的代表键。
    assert_eq!(km.action_for(key!(k)), Some(ReaderAction::ScrollUp));
    assert_eq!(km.action_for(key!(down)), Some(ReaderAction::ScrollDown));
    assert_eq!(km.action_for(key!(pagedown)), Some(ReaderAction::PageDown));
    assert_eq!(km.action_for(key!(home)), Some(ReaderAction::GoTop));
    #[cfg(feature = "tts")]
    {
        assert_eq!(km.action_for(key!('+')), Some(ReaderAction::VolumeUp));
        assert_eq!(km.action_for(key!('-')), Some(ReaderAction::VolumeDown));
    }
    #[cfg(not(feature = "tts"))]
    {
        assert_eq!(km.action_for(key!('+')), None);
        assert_eq!(km.action_for(key!('-')), None);
        assert_eq!(km.action_for(key!(t)), None);
        assert_eq!(km.action_for(key!(shift - t)), None);
    }
    assert_eq!(km.action_for(key!(tab)), Some(ReaderAction::ToggleReadMode));
    // i/I、t/T 大小写双绑定(I = shift-i)。
    assert_eq!(km.action_for(key!(i)), Some(ReaderAction::ToggleInfo));
    assert_eq!(
        km.action_for(key!(shift - i)),
        Some(ReaderAction::ToggleInfo)
    );
    #[cfg(feature = "tts")]
    assert_eq!(
        km.action_for(key!(shift - t)),
        Some(ReaderAction::ToggleTts)
    );
    assert_eq!(
        km.action_for(key!(o)),
        Some(ReaderAction::ToggleReaderSettings)
    );
    // 整页翻页的 Vim 绑定与物理键并存。
    assert_eq!(km.action_for(key!(ctrl - f)), Some(ReaderAction::PageDown));
    assert_eq!(km.action_for(key!(ctrl - b)), Some(ReaderAction::PageUp));
}

/// 用户覆盖经 [reader] 表合并后生效,未覆盖的 action 不受影响。
#[test]
fn reader_override_merges() {
    let mut km = reader_defaults();
    let table: ratatui_kit_keymap::toml::Table =
        ratatui_kit_keymap::toml::from_str("page_down = [\"ctrl-d\"]").unwrap();
    let warnings = km.merge_toml_table(table);
    assert!(warnings.is_empty());
    assert_eq!(km.action_for(key!(ctrl - d)), Some(ReaderAction::PageDown));
    // 覆盖是整体替换:该 action 的全部默认键(含 ctrl-f)一并失效。
    assert_eq!(km.action_for(key!(pagedown)), None);
    assert_eq!(km.action_for(key!(ctrl - f)), None);
    assert_eq!(km.action_for(key!(k)), Some(ReaderAction::ScrollUp));
}

/// 显示层沿用「↑ / K」视觉习惯:方向键箭头、单字符大写、覆盖后显示新键。
#[test]
fn display_keys_follow_project_style() {
    let mut km = reader_defaults();
    assert_eq!(display_keys(&km, ReaderAction::ScrollUp), "↑ / K");
    assert_eq!(
        display_keys(&km, ReaderAction::VolumeDown),
        if cfg!(feature = "tts") { "-" } else { "" }
    );
    // t 与 shift-t 双绑定折叠为单个「T」(同迁移前帮助显示)。
    assert_eq!(
        display_keys(&km, ReaderAction::ToggleTts),
        if cfg!(feature = "tts") { "T" } else { "" }
    );
    let table: ratatui_kit_keymap::toml::Table =
        ratatui_kit_keymap::toml::from_str("page_down = [\"ctrl-d\"]").unwrap();
    km.merge_toml_table(table);
    assert_eq!(display_keys(&km, ReaderAction::PageDown), "Ctrl-d");
}

/// 告警渲染为中文且逐条对应。
#[test]
fn warnings_render_in_chinese() {
    let mut km = reader_defaults();
    let table: ratatui_kit_keymap::toml::Table =
        ratatui_kit_keymap::toml::from_str("page_down = \"ctrl-\"\ntypo = \"x\"\ntoggle_title = 3")
            .unwrap();
    let messages: Vec<String> = km
        .merge_toml_table(table)
        .iter()
        .map(render_warning)
        .collect();
    assert_eq!(messages.len(), 3);
    assert!(messages.iter().any(|m| m.contains("无法解析")));
    assert!(messages.iter().any(|m| m.contains("未知操作")));
    assert!(messages.iter().any(|m| m.contains("值类型不对")));
    // 全部回退/忽略后默认键完好。
    assert_eq!(km.action_for(key!(pagedown)), Some(ReaderAction::PageDown));
}

#[cfg(not(feature = "tts"))]
#[test]
fn basic_ignores_legacy_listening_overrides_without_changing_data() {
    let original: ratatui_kit_keymap::toml::Table = ratatui_kit_keymap::toml::from_str(
        "toggle_play = [\"p\"]\nvolume_up = [\"+\"]\ntoggle_tts = [\"t\"]\npage_down = [\"ctrl-d\"]"
    ).unwrap();
    let mut map = reader_defaults();
    assert!(
        map.merge_toml_table(active_reader_overrides(original.clone()))
            .is_empty()
    );
    assert!(original.contains_key("toggle_play"));
    assert_eq!(map.action_for(key!(p)), None);
    assert_eq!(map.action_for(key!(t)), None);
    assert_eq!(map.action_for(key!(ctrl - d)), Some(ReaderAction::PageDown));
}

#[test]
fn follow_playback_is_feature_gated_and_remappable() {
    let mut km = reader_defaults();
    assert_eq!(
        display_keys(&km, ReaderAction::FollowPlayback),
        if cfg!(feature = "tts") { "F" } else { "" }
    );
    #[cfg(feature = "tts")]
    {
        assert!(
            km.merge_toml_table(
                ratatui_kit_keymap::toml::from_str("follow_playback = [\"r\"]").unwrap()
            )
            .is_empty()
        );
        assert_eq!(display_keys(&km, ReaderAction::FollowPlayback), "R");
    }
    #[cfg(not(feature = "tts"))]
    let _ = &mut km;
}