diff --git a/src/main.rs b/src/main.rs index ecc1d9c..3640662 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1480,7 +1480,7 @@ fn main() { #[cfg(test)] mod tests { - use super::find_urls; + use super::{base64_encode, find_urls}; use crate::terminal::TerminalState; /// Write `text` into row 0 of a freshly-created state. @@ -1622,4 +1622,55 @@ mod tests { let u = "https://example.com/path?q=1&r=2#frag"; assert_eq!(urls(u), vec![u]); } + + // ── base64_encode ───────────────────────────────────────────────────────── + + #[test] + fn base64_empty() { + assert_eq!(base64_encode(b""), ""); + } + + #[test] + fn base64_well_known_vectors() { + // RFC 4648 test vectors + assert_eq!(base64_encode(b"f"), "Zg=="); + assert_eq!(base64_encode(b"fo"), "Zm8="); + assert_eq!(base64_encode(b"foo"), "Zm9v"); + assert_eq!(base64_encode(b"foob"), "Zm9vYg=="); + assert_eq!(base64_encode(b"fooba"), "Zm9vYmE="); + assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy"); + } + + #[test] + fn base64_hello() { + assert_eq!(base64_encode(b"hello"), "aGVsbG8="); + } + + #[test] + fn base64_all_zeros() { + assert_eq!(base64_encode(&[0u8; 3]), "AAAA"); + } + + #[test] + fn base64_output_length_always_multiple_of_four() { + for len in 0..=9 { + let data = vec![0xffu8; len]; + let encoded = base64_encode(&data); + assert_eq!(encoded.len() % 4, 0, "length {len} → encoded len {}", encoded.len()); + } + } + + #[test] + fn base64_padding_one_equals() { + // 2-byte input → 3 output chars + 1 '=' + let enc = base64_encode(b"ab"); + assert!(enc.ends_with('=') && !enc.ends_with("=="), "got: {enc}"); + } + + #[test] + fn base64_padding_two_equals() { + // 1-byte input → 2 output chars + "==" + let enc = base64_encode(b"a"); + assert!(enc.ends_with("=="), "got: {enc}"); + } } diff --git a/src/terminal.rs b/src/terminal.rs index 97a219e..2e0f6d7 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1232,6 +1232,71 @@ mod tests { t.process(LEAVE_ALT); assert_eq!(ch(&t, 0, 0), 'C'); // normal content restored } + + // ── Bracketed paste mode ────────────────────────────────────────────────── + + #[test] + fn bracketed_paste_enabled_by_2004h() { + let mut t = t(80, 24); + assert!(!t.state.bracketed_paste); + t.process(b"\x1b[?2004h"); + assert!(t.state.bracketed_paste); + } + + #[test] + fn bracketed_paste_disabled_by_2004l() { + let mut t = t(80, 24); + t.process(b"\x1b[?2004h"); + t.process(b"\x1b[?2004l"); + assert!(!t.state.bracketed_paste); + } + + #[test] + fn bracketed_paste_toggle_independent_of_alt_screen() { + let mut t = t(80, 24); + t.process(b"\x1b[?2004h"); + t.process(ENTER_ALT); + assert!(t.state.bracketed_paste); + t.process(LEAVE_ALT); + assert!(t.state.bracketed_paste); + } + + // ── OSC 52 clipboard query ──────────────────────────────────────────────── + + #[test] + fn osc_52_query_sets_flag() { + let mut t = t(80, 24); + assert!(!t.state.osc_52_query); + // vte splits OSC params on ';', so params will be ["52", "c", "?"] + t.process(b"\x1b]52;c;?\x07"); + assert!(t.state.osc_52_query); + } + + #[test] + fn osc_52_query_flag_via_dispatch() { + // Direct osc_dispatch call — isolates parsing from vte. + let mut s = TerminalState::new(80, 24); + s.osc_dispatch(&[b"52", b"c", b"?"], false); + assert!(s.osc_52_query); + } + + #[test] + fn osc_52_non_query_does_not_set_flag() { + let mut s = TerminalState::new(80, 24); + // A write (non-"?") payload must not set the query flag. + s.osc_dispatch(&[b"52", b"c", b"aGVsbG8="], false); + assert!(!s.osc_52_query); + } + + #[test] + fn osc_52_query_flag_cleared_externally() { + let mut t = t(80, 24); + t.process(b"\x1b]52;c;?\x07"); + assert!(t.state.osc_52_query); + // Simulate host clearing the flag after responding. + t.state.osc_52_query = false; + assert!(!t.state.osc_52_query); + } } pub struct Terminal {