trnovel 0.18.2

Terminal reader for novel
use super::*;
use ratatui_kit::test_util::render_frames;

fn catalog() -> Snapshot {
    let caps = tts_protocol::Capabilities {
        backend: "moss".into(),
        model: Some("nano".into()),
        model_name: "MOSS Nano".into(),
        default_voice: "A".into(),
        voices: vec!["A".into(), "B".into()],
        voice_names: [("A".into(), "甲".into()), ("B".into(), "乙".into())].into(),
        compiled_devices: vec![Device::Cpu],
        native_streaming: true,
        style: false,
        cloning: false,
        pronunciation: false,
        continuation: true,
        parameters: vec![],
    };
    let mut qwen = caps.clone();
    qwen.backend = "qwen".into();
    qwen.model = Some("custom".into());
    qwen.model_name = "Qwen Custom".into();
    qwen.style = true;
    qwen.compiled_devices = vec![Device::Metal];
    let config = tts_protocol::Config {
        backend: "moss".into(),
        model: Some("nano".into()),
        voice: "A".into(),
        ..Default::default()
    };
    let mut snapshot = Snapshot::default();
    snapshot.config = Some(config.clone());
    snapshot.sound = Some(SoundConfig::from(&config));
    snapshot.backends = vec![caps, qwen];
    snapshot
}
#[test]
fn live_filter_matches_display_names_ids_and_models_without_changing_selection() {
    let snapshot = catalog();
    assert_eq!(
        picker::choices(&snapshot, PickerKind::Model, "QWEN").len(),
        1
    );
    assert_eq!(picker::choices(&snapshot, PickerKind::Voice, "乙").len(), 1);
    assert_eq!(picker::choices(&snapshot, PickerKind::Voice, "b").len(), 1);
    assert!(picker::choices(&snapshot, PickerKind::Model, "absent").is_empty());
    assert_eq!(snapshot.sound.unwrap().voice, "A");
}
#[test]
fn sound_rows_follow_pending_model_capabilities_and_remove_alignment() {
    let mut snapshot = catalog();
    assert!(
        !rows(&snapshot, Panel::Sound)
            .iter()
            .any(|row| row.label == "朗读风格")
    );
    snapshot.sound.as_mut().unwrap().backend = "qwen".into();
    snapshot.sound.as_mut().unwrap().model = Some("custom".into());
    assert!(
        rows(&snapshot, Panel::Sound)
            .iter()
            .any(|row| row.label == "朗读风格")
    );
    let sound_rows = rows(&snapshot, Panel::Sound);
    let apply = sound_rows
        .iter()
        .position(|row| row.label == "应用并重播")
        .unwrap();
    assert!(
        sound_rows
            .iter()
            .position(|row| row.label == "从头重听")
            .is_some_and(|replay| replay == apply + 1)
    );
    assert_eq!(rows(&snapshot, Panel::Playback).len(), 6);
    assert!(
        rows(&snapshot, Panel::Playback)
            .iter()
            .any(|row| row.label == "播放策略" && row.value == "流式播放")
    );
    for panel in [Panel::Playback, Panel::Sound, Panel::Advanced] {
        assert!(
            !rows(&snapshot, panel)
                .iter()
                .any(|row| row.label.contains("对齐") || row.label == "逐句高亮")
        );
    }
}
#[derive(Props)]
struct SceneProps {
    handle: super::super::Handle,
    snapshot: Snapshot,
}
#[component]
fn Scene(props: &SceneProps, mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
    let context = TtsContext {
        handle: props.handle.clone(),
        snapshot: hooks.use_state(|| props.snapshot.clone()),
    };
    element!(PaletteProvider(palette:Palette::default()) {ContextProvider(value:Context::owned(context)) {
        picker::SoundPicker(kind:Some(PickerKind::Model))
    }})
}
fn text(buffer: &ratatui::buffer::Buffer) -> String {
    buffer
        .content
        .iter()
        .flat_map(|cell| cell.symbol().chars())
        .filter(|c| !c.is_whitespace())
        .collect()
}
#[tokio::test]
async fn actual_picker_renders_at_normal_and_small_terminal_sizes() {
    let (handle, task) = super::super::Handle::new();
    for (width, height) in [(100, 32), (52, 18)] {
        let display = render_frames(
            element!(Scene(
                ..SceneProps {
                    handle: handle.clone(),
                    snapshot: catalog(),
                }
            )),
            width,
            height,
            3,
        );
        assert!(text(&display).contains("搜索名称或ID"));
        assert!(text(&display).contains("MOSSNano"));
    }
    handle.shutdown().await;
    task.await.unwrap();
}