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 da1_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[c");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::Da1));
}

#[test]
fn da2_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[>c");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::Da2));
}

#[test]
fn da3_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[=c");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::Da3));
}

#[test]
fn dsr_status_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[5n");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::DsrStatus));
}

#[test]
fn dsr_cursor_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[6n");
    let events = parser.screen_mut().drain_events();
    assert!(
        events
            .iter()
            .any(|e| matches!(e, ScreenEvent::DsrCursorPosition { .. }))
    );
}

#[test]
fn xtversion_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[>q");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::Xtversion));
}

#[test]
fn decrqm_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?7$p");
    let events = parser.screen_mut().drain_events();
    assert!(
        events
            .iter()
            .any(|e| matches!(e, ScreenEvent::Decrqm { mode: 7, .. }))
    );
}

#[test]
fn ansi_mode_report_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[4$p");
    let events = parser.screen_mut().drain_events();
    assert!(
        events
            .iter()
            .any(|e| matches!(e, ScreenEvent::AnsiModeReport { mode: 4, .. }))
    );
}

#[test]
fn decrqss_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1bP$qm\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.iter().any(|e| matches!(
        e,
        ScreenEvent::Decrqss { query, .. } if query == b"m"
    )));
}

#[test]
fn osc10_query_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]10;?\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.iter().any(|e| matches!(
        e,
        ScreenEvent::ColorQuery {
            target: ColorTarget::Foreground,
            ..
        }
    )));
}

#[test]
fn osc4_query_emits_event() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]4;1;?\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.iter().any(|e| matches!(
        e,
        ScreenEvent::ColorQuery {
            target: ColorTarget::Palette(1),
            ..
        }
    )));
}

#[test]
fn title_changed_osc2() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]2;hello\x07");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::TitleChanged));
    assert_eq!(parser.screen().title(), "hello");
}

#[test]
fn title_changed_osc0_sets_both() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]0;world\x07");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::TitleChanged));
    assert_eq!(parser.screen().title(), "world");
    assert_eq!(parser.screen().icon_name(), "world");
}

#[test]
fn mode_changed_bracketed_paste() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?2004h");
    let events = parser.screen_mut().drain_events();
    assert!(
        events.contains(&ScreenEvent::ModeChanged {
            mode: TerminalMode::BracketedPaste,
            enabled: true,
        }),
        "DECSET 2004 must surface a ModeChanged(BracketedPaste, enabled=true) event",
    );
    assert!(parser.screen().mode(TerminalMode::BracketedPaste));

    process(&mut parser, b"\x1b[?2004l");
    let events = parser.screen_mut().drain_events();
    assert!(
        events.contains(&ScreenEvent::ModeChanged {
            mode: TerminalMode::BracketedPaste,
            enabled: false,
        }),
        "DECRST 2004 must surface a ModeChanged(BracketedPaste, enabled=false) event",
    );
    assert!(!parser.screen().mode(TerminalMode::BracketedPaste));
}

#[test]
fn mode_changed_not_emitted_for_internal() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?25l"); // hide cursor (internal mode)
    let events = parser.screen_mut().drain_events();
    assert!(events.is_empty());
}

#[test]
fn mode_changed_not_emitted_when_unchanged() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b[?2004h");
    parser.screen_mut().drain_events();
    // set again, no change so no event
    process(&mut parser, b"\x1b[?2004h");
    let events = parser.screen_mut().drain_events();
    assert!(
        !events
            .iter()
            .any(|e| matches!(e, ScreenEvent::ModeChanged { .. })),
        "redundant DECSET on an already-set mode must not emit a ModeChanged event",
    );
}

#[test]
fn color_set_osc10() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]10;rgb:ff/00/80\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::ColorSet {
        target: ColorTarget::Foreground,
        color: crate::attrs::Color::Rgb(255, 0, 128),
    }));
}

#[test]
fn palette_set_osc4() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]4;3;rgb:ff/ff/00\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::PaletteColorSet {
        index: 3,
        color: crate::attrs::Color::Rgb(255, 255, 0),
    }));
}

#[test]
fn color_reset_osc110() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    // set, then reset
    process(&mut parser, b"\x1b]10;rgb:ff/00/00\x1b\\");
    parser.screen_mut().drain_events();
    process(&mut parser, b"\x1b]110\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::ColorReset(ColorTarget::Foreground)));
}

#[test]
fn palette_reset_osc104() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]4;1;rgb:ff/00/00\x1b\\");
    parser.screen_mut().drain_events();
    process(&mut parser, b"\x1b]104;1\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::PaletteColorReset { index: Some(1) }));
}

#[test]
fn palette_reset_all_osc104() {
    let mut parser = crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
    process(&mut parser, b"\x1b]104\x1b\\");
    let events = parser.screen_mut().drain_events();
    assert!(events.contains(&ScreenEvent::PaletteColorReset { index: None }));
}