//! Tests for `bxctl::escape`: text the model wrote is printed as data. Do not edit. //! //! The expected escapes are built by `esc`, never spelled out, so that nothing that handles this //! file can turn one into the character it stands for. use bxctl::escape::{escape_json_text, escape_model_text}; const BACKSLASH: char = '\\'; /// The escape for one code point: a backslash, `u`, and four lowercase hex digits. fn esc(code: u32) -> String { format!("{BACKSLASH}u{code:04x}") } fn ch(code: u32) -> char { char::from_u32(code).unwrap() } /// Every code point that must be escaped, as inclusive ranges. const HIDDEN: [(u32, u32); 6] = [ (0x0000, 0x001f), (0x007f, 0x009f), (0x200b, 0x200f), (0x2028, 0x202e), (0x2060, 0x2069), (0xfeff, 0xfeff), ]; fn hidden(code: u32) -> bool { HIDDEN.iter().any(|(lo, hi)| (*lo..=*hi).contains(&code)) } /// Walks every code point below U+11000, not a sample: each is either escaped exactly or left /// exactly as it is. #[test] fn every_listed_code_point_is_escaped_and_no_other() { let mut escaped = 0; for code in 0..0x11000u32 { let Some(c) = char::from_u32(code) else { continue; // the surrogates are not characters }; let text = format!("a{c}b"); let got = escape_json_text(&text); if hidden(code) { escaped += 1; assert_eq!(got, format!("a{}b", esc(code)), "U+{code:04X}"); } else { assert_eq!(got, text, "U+{code:04X} must pass through"); } } assert_eq!(escaped, 32 + 33 + 5 + 7 + 10 + 1, "the six ranges, in full"); } #[test] fn the_edges_of_each_range() { for (lo, hi) in HIDDEN { assert_eq!(escape_json_text(&ch(lo).to_string()), esc(lo)); assert_eq!(escape_json_text(&ch(hi).to_string()), esc(hi)); if lo > 0 { let before = ch(lo - 1).to_string(); assert_eq!(escape_json_text(&before), before, "U+{:04X}", lo - 1); } let after = ch(hi + 1).to_string(); assert_eq!(escape_json_text(&after), after, "U+{:04X}", hi + 1); } } #[test] fn hex_digits_are_lowercase_and_there_are_always_four() { assert_eq!(escape_json_text("\x1b"), esc(0x1b)); assert!(escape_json_text("\x1b").ends_with("001b")); assert!(escape_json_text("\0").ends_with("0000")); assert!(escape_json_text(&ch(0xfeff).to_string()).ends_with("feff")); assert!(escape_json_text(&ch(0x202e).to_string()).ends_with("202e")); } #[test] fn an_escape_sequence_cannot_reach_the_terminal() { let text = "before\x1b[8mhidden\x1b[0m\x07after"; let got = escape_json_text(text); assert!(!got.contains('\x1b') && !got.contains('\x07'), "{got:?}"); assert_eq!( got, format!( "before{}[8mhidden{}[0m{}after", esc(0x1b), esc(0x1b), esc(0x07) ) ); } #[test] fn a_path_cannot_be_shown_backwards() { // U+202E makes a terminal draw what follows from right to left. let text = format!("/home/kyle/notes/{}dm.terces", ch(0x202e)); assert_eq!( escape_json_text(&text), format!("/home/kyle/notes/{}dm.terces", esc(0x202e)) ); let text = format!("a{}b{}c", ch(0x200b), ch(0x2066)); assert_eq!( escape_json_text(&text), format!("a{}b{}c", esc(0x200b), esc(0x2066)) ); } #[test] fn ordinary_text_is_unchanged() { for text in [ "", "plain", r#"{"command":"ls -l","cwd":"/home/kyle"}"#, "naïve café 日本語 🙂", "a backslash \\ and a quote \" stay as they are", ] { assert_eq!(escape_json_text(text), text); assert_eq!(escape_model_text(text), text); } } #[test] fn json_text_escapes_newline_and_tab_but_model_text_keeps_them() { let text = "one\n\ttwo\r\n"; assert_eq!( escape_json_text(text), format!("one{}{}two{}{}", esc(0x0a), esc(0x09), esc(0x0d), esc(0x0a)) ); assert_eq!( escape_model_text(text), format!("one\n\ttwo{}\n", esc(0x0d)), "only newline and tab pass; a carriage return could overwrite the line" ); } #[test] fn model_text_escapes_everything_else_the_same_way() { for (lo, hi) in HIDDEN { for code in lo..=hi { if code == 0x0a || code == 0x09 { continue; } assert_eq!( escape_model_text(&ch(code).to_string()), esc(code), "U+{code:04X}" ); } } }