tastty-core 0.1.0

Sans-IO core of the tastty terminal session library: VT parser, screen buffer, and byte encoders.
use super::*;

#[test]
fn xtsave_xtrestore_roundtrips_multiple_modes() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(
        &mut parser,
        b"\x1b[?2004h\x1b[?1004h\x1b[?2004;1004s\x1b[?2004l\x1b[?1004l\x1b[?2004;1004r",
    );
    assert!(parser.screen().mode(TerminalMode::BracketedPaste));
    assert!(parser.screen().mode(TerminalMode::FocusInOut));
}

#[test]
fn xtsave_with_no_params_saves_all_modes() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(
        &mut parser,
        b"\x1b[?2004h\x1b[?1004h\x1b[?s\x1b[?2004l\x1b[?1004l\x1b[?r",
    );
    assert!(parser.screen().mode(TerminalMode::BracketedPaste));
    assert!(parser.screen().mode(TerminalMode::FocusInOut));
}

#[test]
fn xtrestore_without_prior_save_is_noop() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?2004h\x1b[?2004r");
    assert!(
        parser.screen().mode(TerminalMode::BracketedPaste),
        "XTRESTORE for a never-saved mode must not change its current state",
    );

    process(&mut parser, b"\x1b[?2004l\x1b[?2004r");
    assert!(
        !parser.screen().mode(TerminalMode::BracketedPaste),
        "XTRESTORE for a never-saved mode must still leave it cleared",
    );
}

#[test]
fn xtsave_overwrites_previous_snapshot() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    // Save (off, fresh state), then enable, save again (on), disable,
    // restore: the second save must win.
    process(
        &mut parser,
        b"\x1b[?2004s\x1b[?2004h\x1b[?2004s\x1b[?2004l\x1b[?2004r",
    );
    assert!(
        parser.screen().mode(TerminalMode::BracketedPaste),
        "second XTSAVE must overwrite the first; restore must reapply the on bit",
    );
}

#[test]
fn xtrestore_emits_mode_changed_on_transition() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?2004h\x1b[?2004s\x1b[?2004l");
    parser.screen_mut().drain_events();

    process(&mut parser, b"\x1b[?2004r");
    let events = parser.screen_mut().drain_events();
    assert!(
        events.contains(&ScreenEvent::ModeChanged {
            mode: TerminalMode::BracketedPaste,
            enabled: true,
        }),
        "XTRESTORE re-enabling BracketedPaste must emit ModeChanged; got {events:?}",
    );
}

#[test]
fn xtsave_unrecognized_mode_is_ignored() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    // 9999 is unrecognized; XTSAVE must be a no-op for it. A
    // subsequent enable+restore must therefore leave the mode alone.
    process(&mut parser, b"\x1b[?9999s\x1b[?2004h\x1b[?9999r");
    assert!(
        parser.screen().mode(TerminalMode::BracketedPaste),
        "XTRESTORE for an unrecognized mode must not touch unrelated state",
    );
}

#[test]
fn xtsave_mode_9_round_trip() {
    // Mode 9 (X10 mouse compatibility) is a pure on/off DEC private
    // mode; XTSAVE/XTRESTORE must snapshot and reapply its bit.
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?9h\x1b[?9s\x1b[?9l\x1b[?9r");
    assert!(
        parser.screen().mode(TerminalMode::MouseReportX10),
        "XTRESTORE ?9 must reapply the X10 mouse bit saved by XTSAVE ?9",
    );
}

#[test]
fn xtsave_mode_67_round_trip() {
    // Mode 67 (DECBKM) controls the byte the encoder emits for
    // Backspace; XTSAVE/XTRESTORE must snapshot and reapply its bit
    // explicitly, not only via the empty-params save-all path.
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?67h\x1b[?67s\x1b[?67l\x1b[?67r");
    assert!(
        parser.screen().mode(TerminalMode::BackspaceBs),
        "XTRESTORE ?67 must reapply the DECBKM bit saved by XTSAVE ?67",
    );
}