use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use proptest::prelude::*;
use std::path::PathBuf;
use tempfile::TempDir;
const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
const UTF16_LE_BOM: &[u8] = &[0xFF, 0xFE];
const UTF16_BE_BOM: &[u8] = &[0xFE, 0xFF];
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TestEncoding {
Utf8,
Utf8Bom,
Utf16Le,
Utf16Be,
Latin1,
Windows1252,
Windows1250,
Gb18030,
Ascii,
}
impl TestEncoding {
fn encode(&self, text: &str) -> Vec<u8> {
match self {
TestEncoding::Utf8 => text.as_bytes().to_vec(),
TestEncoding::Utf8Bom => {
let mut result = UTF8_BOM.to_vec();
result.extend_from_slice(text.as_bytes());
result
}
TestEncoding::Utf16Le => {
let mut result = UTF16_LE_BOM.to_vec();
for ch in text.encode_utf16() {
result.extend_from_slice(&ch.to_le_bytes());
}
result
}
TestEncoding::Utf16Be => {
let mut result = UTF16_BE_BOM.to_vec();
for ch in text.encode_utf16() {
result.extend_from_slice(&ch.to_be_bytes());
}
result
}
TestEncoding::Latin1 => {
text.chars()
.map(|c| {
if c as u32 <= 0xFF {
c as u8
} else {
b'?' }
})
.collect()
}
TestEncoding::Windows1252 => {
text.chars()
.map(|c| {
if c as u32 <= 0xFF {
c as u8
} else {
b'?' }
})
.collect()
}
TestEncoding::Windows1250 => {
let mut result = Vec::new();
for c in text.chars() {
let byte = match c {
'ą' => 0xB9,
'ć' => 0xE6,
'ę' => 0xEA,
'ł' => 0xB3,
'ń' => 0xF1,
'ó' => 0xF3,
'ś' => 0x9C,
'ź' => 0x9F,
'ż' => 0xBF,
'Ą' => 0xA5,
'Ć' => 0xC6,
'Ę' => 0xCA,
'Ł' => 0xA3,
'Ń' => 0xD1,
'Ó' => 0xD3,
'Ś' => 0x8C,
'Ź' => 0x8F,
'Ż' => 0xAF,
'á' => 0xE1,
'č' => 0xE8,
'ď' => 0xEF,
'é' => 0xE9,
'ě' => 0xEC,
'í' => 0xED,
'ň' => 0xF2,
'ř' => 0xF8,
'š' => 0x9A,
'ť' => 0x9D,
'ú' => 0xFA,
'ů' => 0xF9,
'ý' => 0xFD,
'ž' => 0x9E,
'Á' => 0xC1,
'Č' => 0xC8,
'Ď' => 0xCF,
'É' => 0xC9,
'Ě' => 0xCC,
'Í' => 0xCD,
'Ň' => 0xD2,
'Ř' => 0xD8,
'Š' => 0x8A,
'Ť' => 0x8D,
'Ú' => 0xDA,
'Ů' => 0xD9,
'Ý' => 0xDD,
'Ž' => 0x8E,
c if c.is_ascii() => c as u8,
_ => b'?', };
result.push(byte);
}
result
}
TestEncoding::Gb18030 => {
let mut result = Vec::new();
for c in text.chars() {
match c {
'你' => result.extend_from_slice(&[0xC4, 0xE3]),
'好' => result.extend_from_slice(&[0xBA, 0xC3]),
'世' => result.extend_from_slice(&[0xCA, 0xC0]),
'界' => result.extend_from_slice(&[0xBD, 0xE7]),
'\n' => result.push(0x0A),
'\r' => result.push(0x0D),
c if c.is_ascii() => result.push(c as u8),
_ => result.push(b'?'),
}
}
result
}
TestEncoding::Ascii => {
text.chars()
.map(|c| if c.is_ascii() { c as u8 } else { b'?' })
.collect()
}
}
}
#[allow(dead_code)]
fn can_encode_losslessly(&self, text: &str) -> bool {
match self {
TestEncoding::Utf8
| TestEncoding::Utf8Bom
| TestEncoding::Utf16Le
| TestEncoding::Utf16Be => true,
TestEncoding::Latin1 | TestEncoding::Windows1252 => {
text.chars().all(|c| (c as u32) <= 0xFF)
}
TestEncoding::Windows1250 => {
text.chars().all(|c| {
c.is_ascii()
|| matches!(
c,
'ą' | 'ć'
| 'ę'
| 'ł'
| 'ń'
| 'ó'
| 'ś'
| 'ź'
| 'ż'
| 'Ą'
| 'Ć'
| 'Ę'
| 'Ł'
| 'Ń'
| 'Ó'
| 'Ś'
| 'Ź'
| 'Ż'
| 'á'
| 'č'
| 'ď'
| 'é'
| 'ě'
| 'í'
| 'ň'
| 'ř'
| 'š'
| 'ť'
| 'ú'
| 'ů'
| 'ý'
| 'ž'
| 'Á'
| 'Č'
| 'Ď'
| 'É'
| 'Ě'
| 'Í'
| 'Ň'
| 'Ř'
| 'Š'
| 'Ť'
| 'Ú'
| 'Ů'
| 'Ý'
| 'Ž'
)
})
}
TestEncoding::Ascii => text.is_ascii(),
TestEncoding::Gb18030 => {
text.chars().all(|c| {
c.is_ascii() || c == '\n' || c == '\r' || matches!(c, '你' | '好' | '世' | '界')
})
}
}
}
#[allow(dead_code)]
fn display_name(&self) -> &'static str {
match self {
TestEncoding::Utf8 => "UTF-8",
TestEncoding::Utf8Bom => "UTF-8 BOM",
TestEncoding::Utf16Le => "UTF-16 LE",
TestEncoding::Utf16Be => "UTF-16 BE",
TestEncoding::Latin1 => "Latin-1",
TestEncoding::Windows1252 => "Windows-1252",
TestEncoding::Windows1250 => "Windows-1250",
TestEncoding::Gb18030 => "GB18030",
TestEncoding::Ascii => "ASCII",
}
}
}
fn create_encoded_file(dir: &TempDir, name: &str, encoding: TestEncoding, text: &str) -> PathBuf {
let path = dir.path().join(name);
let bytes = encoding.encode(text);
std::fs::write(&path, &bytes).unwrap();
path
}
fn ascii_text_strategy() -> impl Strategy<Value = String> {
"[a-zA-Z0-9 ,.!?\\-_]{1,100}"
}
fn latin1_text_strategy() -> impl Strategy<Value = String> {
let ascii_prefix = prop::sample::select(vec![
"Hello ", "Cafe ", "Text ", "File ", "Data ", "The ", "A ", "Test ", "Word ",
]);
let latin1_chars = prop::collection::vec(
prop::sample::select(vec![
'é', 'è', 'ê', 'ë', 'à', 'â', 'ä', 'ç', 'ô', 'ö', 'ù', 'û', 'ü', 'ñ', 'ß', 'æ', 'ø',
'å', 'ÿ', 'þ', 'ý', 'ü', 'û', 'ú', ' ', ' ', ' ',
]),
3..30,
);
let ascii_suffix = prop::sample::select(vec![" end", " ok", ".", "", "", ""]);
(ascii_prefix, latin1_chars, ascii_suffix).prop_map(|(prefix, chars, suffix)| {
let middle: String = chars.into_iter().collect();
format!("{}{}{}", prefix, middle, suffix)
})
}
fn chinese_text_strategy() -> impl Strategy<Value = String> {
prop::collection::vec(
prop::sample::select(vec!['你', '好', '世', '界', ' ', '\n']),
1..20,
)
.prop_map(|chars| chars.into_iter().collect())
}
#[allow(dead_code)]
fn mixed_text_strategy() -> impl Strategy<Value = String> {
(ascii_text_strategy(), chinese_text_strategy()).prop_map(|(a, b)| format!("{} {}", a, b))
}
fn encoding_strategy() -> impl Strategy<Value = TestEncoding> {
prop::sample::select(vec![
TestEncoding::Utf8,
TestEncoding::Utf8Bom,
TestEncoding::Utf16Le,
TestEncoding::Utf16Be,
TestEncoding::Latin1,
TestEncoding::Ascii,
])
}
fn unicode_encoding_strategy() -> impl Strategy<Value = TestEncoding> {
prop::sample::select(vec![
TestEncoding::Utf8,
TestEncoding::Utf8Bom,
TestEncoding::Utf16Le,
TestEncoding::Utf16Be,
])
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn prop_ascii_roundtrip(
text in ascii_text_strategy(),
encoding in encoding_strategy()
) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "test.txt", encoding, &text);
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let buffer_content = harness.get_buffer_content().unwrap();
let normalized_text = text.replace("\r\n", "\n").replace('\r', "\n");
let normalized_buffer = buffer_content.replace("\r\n", "\n").replace('\r', "\n");
prop_assert!(
normalized_buffer.contains(&normalized_text.trim()),
"Buffer should contain the text. Expected: {:?}, Got: {:?}",
normalized_text,
normalized_buffer
);
}
#[test]
fn prop_encoding_preserved_on_save(
text in ascii_text_strategy().prop_filter("need content", |s| !s.is_empty()),
encoding in unicode_encoding_strategy()
) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "test.txt", encoding, &text);
let original_bytes = std::fs::read(&file_path).unwrap();
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL).unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved_bytes = std::fs::read(&file_path).unwrap();
prop_assert_eq!(
saved_bytes,
original_bytes,
"File should be unchanged after save without edits"
);
}
#[test]
fn prop_edit_preserves_encoding(
initial_text in ascii_text_strategy().prop_filter("need content", |s| s.len() > 5),
added_text in "[a-zA-Z0-9]{1,20}",
encoding in unicode_encoding_strategy()
) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "test.txt", encoding, &initial_text);
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::End, KeyModifiers::CONTROL).unwrap();
harness.type_text(&added_text).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL).unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved_bytes = std::fs::read(&file_path).unwrap();
match encoding {
TestEncoding::Utf8Bom => {
prop_assert!(
saved_bytes.starts_with(UTF8_BOM),
"UTF-8 BOM should be preserved"
);
}
TestEncoding::Utf16Le => {
prop_assert!(
saved_bytes.starts_with(UTF16_LE_BOM),
"UTF-16 LE BOM should be preserved"
);
}
TestEncoding::Utf16Be => {
prop_assert!(
saved_bytes.starts_with(UTF16_BE_BOM),
"UTF-16 BE BOM should be preserved"
);
}
_ => {}
}
}
#[test]
fn prop_chinese_text_preserved(
text in chinese_text_strategy(),
encoding in unicode_encoding_strategy()
) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "chinese.txt", encoding, &text);
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let buffer_content = harness.get_buffer_content().unwrap();
let normalized_text = text.replace("\r\n", "\n").replace('\r', "\n");
let normalized_buffer = buffer_content.replace("\r\n", "\n").replace('\r', "\n");
for c in normalized_text.chars() {
if !c.is_whitespace() {
prop_assert!(
normalized_buffer.contains(c),
"Character {:?} should be in buffer. Buffer: {:?}",
c,
normalized_buffer
);
}
}
}
#[test]
fn prop_latin1_text_preserved(text in latin1_text_strategy()) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "latin1.txt", TestEncoding::Latin1, &text);
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let buffer_content = harness.get_buffer_content().unwrap();
for c in text.chars() {
if !c.is_whitespace() && c.is_alphabetic() {
prop_assert!(
buffer_content.contains(c),
"Latin-1 character {:?} should be in buffer. Buffer: {:?}",
c,
buffer_content
);
}
}
}
#[test]
fn prop_utf16_bom_preserved(
text in ascii_text_strategy(),
le in prop::bool::ANY
) {
let encoding = if le { TestEncoding::Utf16Le } else { TestEncoding::Utf16Be };
let expected_bom = if le { UTF16_LE_BOM } else { UTF16_BE_BOM };
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "utf16.txt", encoding, &text);
let original_bytes = std::fs::read(&file_path).unwrap();
prop_assert!(
original_bytes.starts_with(expected_bom),
"File should start with {:?} BOM",
if le { "UTF-16 LE" } else { "UTF-16 BE" }
);
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::End, KeyModifiers::CONTROL).unwrap();
harness.type_text("X").unwrap();
harness.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL).unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved_bytes = std::fs::read(&file_path).unwrap();
prop_assert!(
saved_bytes.starts_with(expected_bom),
"BOM should be preserved after save"
);
}
#[test]
fn prop_all_encodings_roundtrip_exact(
text in ascii_text_strategy().prop_filter("need content", |s| !s.trim().is_empty()),
encoding in encoding_strategy()
) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "roundtrip.txt", encoding, &text);
let original_bytes = std::fs::read(&file_path).unwrap();
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL).unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved_bytes = std::fs::read(&file_path).unwrap();
prop_assert_eq!(
saved_bytes,
original_bytes,
"Saved file should be byte-for-byte identical to original for encoding {:?}",
encoding
);
}
#[test]
fn prop_all_encodings_edit_roundtrip(
text in ascii_text_strategy().prop_filter("need content", |s| s.len() >= 3),
added in "[a-zA-Z]{3,10}",
encoding in encoding_strategy()
) {
let temp_dir = TempDir::new().unwrap();
let file_path = create_encoded_file(&temp_dir, "edit_roundtrip.txt", encoding, &text);
let mut harness = EditorTestHarness::new(120, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::End, KeyModifiers::CONTROL).unwrap();
harness.type_text(&added).unwrap();
harness.render().unwrap();
harness.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL).unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved_bytes = std::fs::read(&file_path).unwrap();
match encoding {
TestEncoding::Utf8Bom => {
prop_assert!(
saved_bytes.starts_with(UTF8_BOM),
"UTF-8 BOM should be preserved after edit"
);
}
TestEncoding::Utf16Le => {
prop_assert!(
saved_bytes.starts_with(UTF16_LE_BOM),
"UTF-16 LE BOM should be preserved after edit"
);
}
TestEncoding::Utf16Be => {
prop_assert!(
saved_bytes.starts_with(UTF16_BE_BOM),
"UTF-16 BE BOM should be preserved after edit"
);
}
_ => {}
}
drop(harness);
let mut harness2 = EditorTestHarness::new(120, 30).unwrap();
harness2.open_file(&file_path).unwrap();
harness2.render().unwrap();
let buffer_content = harness2.get_buffer_content().unwrap();
prop_assert!(
buffer_content.contains(&added),
"Added text '{}' should be in reloaded buffer. Buffer: {:?}",
added,
buffer_content
);
}
}
#[test]
fn test_detect_encoding_utf8() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("utf8.txt");
std::fs::write(&file_path, "Hello, World!\n你好世界\n").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Hello, World!");
let screen = harness.screen_to_string();
assert!(screen.contains('你'), "Screen should contain '你'");
assert!(screen.contains('好'), "Screen should contain '好'");
assert!(screen.contains('世'), "Screen should contain '世'");
assert!(screen.contains('界'), "Screen should contain '界'");
assert!(
screen.contains("UTF-8"),
"UTF-8 should be shown in status bar"
);
}
#[test]
fn test_utf8_bom_hidden_but_preserved() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("utf8_bom.txt");
let mut content = Vec::new();
content.extend_from_slice(UTF8_BOM);
content.extend_from_slice("Hello\n".as_bytes());
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
!screen.contains("\u{FEFF}"),
"BOM character should not be visible in content"
);
harness.assert_screen_contains("Hello");
harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
harness.type_text(" World").unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness
.wait_until(|h| !h.editor().active_state().buffer.is_modified())
.unwrap();
let saved = std::fs::read(&file_path).unwrap();
assert!(
saved.starts_with(UTF8_BOM),
"BOM should be preserved after save"
);
}
#[test]
fn test_empty_file_defaults_to_utf8() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("empty.txt");
std::fs::write(&file_path, "").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
!screen.contains("UTF-16") && !screen.contains("GB18030") && !screen.contains("Latin"),
"Empty file should default to UTF-8, not show other encodings"
);
harness.type_text("New content\n").unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness
.wait_until(|h| !h.editor().active_state().buffer.is_modified())
.unwrap();
let saved = std::fs::read(&file_path).unwrap();
assert!(
!saved.starts_with(UTF8_BOM),
"New files should not have BOM by default"
);
assert_eq!(String::from_utf8(saved).unwrap(), "New content\n");
}
#[test]
fn test_binary_with_fake_bom_detected() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("fake_bom.bin");
let mut content = Vec::new();
content.extend_from_slice(UTF16_LE_BOM);
content.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); content.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47]); std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(!screen.is_empty(), "Editor should display something");
}
#[test]
fn test_gb18030_chinese_display() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("gb18030.txt");
let gb18030_bytes: &[u8] = &[
0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7, 0x0A, ];
std::fs::write(&file_path, gb18030_bytes).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains('你'),
"Screen should contain '你': {}",
screen
);
assert!(
screen.contains('好'),
"Screen should contain '好': {}",
screen
);
assert!(
screen.contains('世'),
"Screen should contain '世': {}",
screen
);
assert!(
screen.contains('界'),
"Screen should contain '界': {}",
screen
);
}
#[test]
fn test_latin1_special_chars_display() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("latin1.txt");
let latin1_bytes: &[u8] = &[
0x48, 0xE9, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0xF6, 0x72, 0x6C, 0x64, 0x20, 0x43, 0x61, 0x66, 0xE9, 0x20, 0x72, 0xE9, 0x73, 0x75, 0x6D, 0xE9, 0x20, 0x6E, 0x61, 0xEF, 0x76, 0x65, 0x0A, ];
std::fs::write(&file_path, latin1_bytes).unwrap();
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Héllo");
harness.assert_screen_contains("Wörld");
harness.assert_screen_contains("Café");
}
#[test]
fn test_encoding_shown_in_status_bar() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let content = "Hello UTF-16";
let mut utf16_bytes = vec![0xFF, 0xFE]; for ch in content.encode_utf16() {
utf16_bytes.extend_from_slice(&ch.to_le_bytes());
}
std::fs::write(&file_path, utf16_bytes).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("UTF-16")
|| screen.contains("utf-16")
|| screen.contains("UTF16")
|| screen.contains("utf16"),
"Status bar should show UTF-16 encoding: {}",
screen
);
}
#[test]
fn test_clipboard_preserves_encoded_content() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("clipboard_test.txt");
std::fs::write(&file_path, "Café résumé\n").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.editor_mut().set_clipboard_for_test("".to_string());
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('c'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::End, KeyModifiers::CONTROL)
.unwrap();
harness
.send_key(KeyCode::Char('v'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let buffer = harness.get_buffer_content().unwrap();
assert!(
buffer.matches("Café").count() == 2,
"Should have two copies of Café: {}",
buffer
);
}
#[test]
fn test_large_utf16_file_navigation() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_utf16.txt");
let line = "This is a test line with content\r\n";
let num_lines = 500;
let mut content = Vec::new();
content.extend_from_slice(UTF16_LE_BOM);
for _ in 0..num_lines {
for ch in line.encode_utf16() {
content.extend_from_slice(&ch.to_le_bytes());
}
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("test line");
harness
.send_key(KeyCode::End, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("test line");
}
#[test]
fn test_encoding_indicator_click_opens_selector() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Hello, World! こんにちは").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("UTF-8");
let (col, row) = harness
.find_text_on_screen("UTF-8")
.expect("UTF-8 encoding indicator should be visible in status bar");
harness.mouse_click(col, row).unwrap();
harness.assert_screen_contains("UTF-16");
}
#[test]
fn test_encoding_indicator_always_visible() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Hello, World!").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("ASCII");
}
#[test]
fn test_utf16_encoding_indicator_click_and_change() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test_utf16.txt");
let text = "Hello UTF-16!\nLine 2\n";
let mut content = UTF16_LE_BOM.to_vec();
for ch in text.encode_utf16() {
content.extend_from_slice(&ch.to_le_bytes());
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("UTF-16 LE");
let (col, row) = harness
.find_text_on_screen("UTF-16 LE")
.expect("UTF-16 LE encoding indicator should be visible in status bar");
harness.mouse_click(col, row).unwrap();
harness.assert_screen_contains("Encoding:");
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness.type_text("UTF-8").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("UTF-8");
harness.assert_screen_not_contains("UTF-16 LE");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let saved_content = std::fs::read(&file_path).unwrap();
assert!(
!saved_content.starts_with(UTF16_LE_BOM),
"Saved file should not have UTF-16 LE BOM"
);
let saved_text = String::from_utf8(saved_content).expect("Saved file should be valid UTF-8");
assert!(
saved_text.contains("Hello UTF-16!"),
"Saved file should contain the original text"
);
assert!(
saved_text.contains("Line 2"),
"Saved file should contain all lines"
);
}
#[test]
fn test_utf8_to_utf16_encoding_change() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test_utf8.txt");
let text = "Hello World! こんにちは\nLine 2\n";
std::fs::write(&file_path, text).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("UTF-8");
let (col, row) = harness
.find_text_on_screen("UTF-8")
.expect("UTF-8 encoding indicator should be visible in status bar");
assert!(
row >= 20,
"UTF-8 should be in status bar (bottom of screen), found at row {}",
row
);
harness.mouse_click(col, row).unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Encoding:"),
"Encoding selector should open after clicking. Screen:\n{}",
screen
);
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap(); harness.render().unwrap();
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap(); harness.render().unwrap();
let screen_after_nav = harness.screen_to_string();
assert!(
screen_after_nav.contains("UTF-16 LE"),
"UTF-16 LE should be visible after navigating. Screen:\n{}",
screen_after_nav
);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let final_screen = harness.screen_to_string();
assert!(
final_screen.contains("UTF-16 LE"),
"Encoding should be UTF-16 LE after confirmation. Screen:\n{}",
final_screen
);
harness.assert_screen_contains("Hello World!");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let saved_content = std::fs::read(&file_path).unwrap();
assert!(
saved_content.starts_with(UTF16_LE_BOM),
"Saved file should have UTF-16 LE BOM. Got first bytes: {:?}",
&saved_content[..saved_content.len().min(10)]
);
let utf16_bytes = &saved_content[2..];
let utf16_units: Vec<u16> = utf16_bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
let decoded = String::from_utf16(&utf16_units).expect("Should be valid UTF-16 LE");
assert!(
decoded.contains("Hello World!"),
"Saved file should contain the original text. Got: {}",
decoded
);
assert!(
decoded.contains("こんにちは"),
"Saved file should preserve Japanese characters"
);
}
#[test]
fn test_windows1250_display_and_selector() {
let temp_dir = TempDir::new().unwrap();
let polish_file = temp_dir.path().join("polish.txt");
let windows1250_bytes: &[u8] = &[
0x5A, 0x61, 0xBF, 0xF3, 0xB3, 0xE6, 0x20, 0x67, 0xEA, 0x9C, 0x6C, 0xB9, 0x20, 0x6A, 0x61, 0x9F, 0xF1, 0x0A, ];
std::fs::write(&polish_file, windows1250_bytes).unwrap();
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&polish_file).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains('ż') || screen.contains('ó') || screen.contains('ł'),
"Screen should contain Polish diacritical characters"
);
let utf8_file = temp_dir.path().join("utf8.txt");
std::fs::write(&utf8_file, "Hello World!\n").unwrap();
drop(harness);
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&utf8_file).unwrap();
harness.render().unwrap();
let (col, row) = harness
.find_text_on_screen("ASCII")
.expect("Encoding indicator should be visible");
harness.mouse_click(col, row).unwrap();
harness.assert_screen_contains("Encoding:");
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness.type_text("1250").unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Windows-1250") && screen.contains("Central European"),
"Selector should show Windows-1250 / CP1250 – Central European"
);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Windows-1250");
}
#[test]
fn test_windows1250_encoding_conversions() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("conversion_test.txt");
std::fs::write(&file_path, "Zażółć gęślą\n").unwrap();
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let (col, row) = harness
.find_text_on_screen("UTF-8")
.expect("UTF-8 indicator should be visible");
harness.mouse_click(col, row).unwrap();
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness.type_text("Windows-1250").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved = std::fs::read(&file_path).unwrap();
assert!(
saved.contains(&0xBF) || saved.contains(&0xB3),
"File should be Windows-1250 encoded"
);
let (decoded, _) = encoding_rs::WINDOWS_1250.decode_without_bom_handling(&saved);
assert!(decoded.contains("Zażółć"), "Should preserve Polish text");
drop(harness);
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let (col, row) = harness
.find_text_on_screen("Windows")
.expect("Windows encoding indicator should be visible");
harness.mouse_click(col, row).unwrap();
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness.type_text("UTF-8").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved = std::fs::read(&file_path).unwrap();
let utf8_str = std::str::from_utf8(&saved).expect("File should be valid UTF-8");
assert!(
utf8_str.contains('ó') || utf8_str.contains('ł'),
"UTF-8 file should contain some Polish chars. Got: {}",
utf8_str
);
}
#[test]
fn test_windows1250_czech_pangram() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("czech_pangram.txt");
let windows1250_bytes: &[u8] = &[
0x50, 0xF8, 0xED, 0x6C, 0x69, 0x9A, 0x20, 0x9E, 0x6C, 0x75, 0x9D, 0x6F, 0x75, 0xE8, 0x6B, 0xFD, 0x20, 0x6B, 0xF9, 0xF2, 0x20, 0xFA, 0x70, 0xEC, 0x6C, 0x20, 0xEF, 0xE1, 0x62, 0x65, 0x6C, 0x73, 0x6B, 0xE9, 0x20, 0xF3, 0x64, 0x79, 0x0A, ];
std::fs::write(&file_path, windows1250_bytes).unwrap();
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("Windows-1250"),
"Should detect as Windows-1250 (contains ť = 0x9D). Screen:\n{}",
screen
);
assert!(screen.contains('í'), "Screen should contain 'í'");
assert!(screen.contains('á'), "Screen should contain 'á'");
assert!(screen.contains('é'), "Screen should contain 'é'");
assert!(screen.contains('ó'), "Screen should contain 'ó'");
assert!(screen.contains('š'), "Screen should contain 'š'");
assert!(screen.contains('ž'), "Screen should contain 'ž'");
assert!(
screen.contains('ř'),
"Screen should contain 'ř' (Windows-1250 specific)"
);
assert!(
screen.contains('ů'),
"Screen should contain 'ů' (Windows-1250 specific)"
);
assert!(
screen.contains('ě'),
"Screen should contain 'ě' (Windows-1250 specific)"
);
assert!(
screen.contains('č'),
"Screen should contain 'č' (Windows-1250 specific)"
);
assert!(
screen.contains('ť'),
"Screen should contain 'ť' (Windows-1250 specific)"
);
let buffer = harness.get_buffer_content().unwrap();
assert!(
buffer.contains("Příliš") || buffer.contains("P"),
"Buffer should contain the pangram. Got: {}",
buffer
);
}
#[test]
fn test_all_encodings_load_and_save_as_utf8() {
struct TestCase {
desc: &'static str,
bytes: &'static [u8],
expected_substr: &'static str,
}
let test_cases: &[TestCase] = &[
TestCase {
desc: "UTF-16 LE",
bytes: &[
0xFF, 0xFE, 0x48, 0x00, 0xE9, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x0A, 0x00,
],
expected_substr: "H",
},
TestCase {
desc: "UTF-16 BE",
bytes: &[
0xFE, 0xFF, 0x00, 0x48, 0x00, 0xE9, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x0A,
],
expected_substr: "H",
},
TestCase {
desc: "UTF-8 BOM",
bytes: &[0xEF, 0xBB, 0xBF, 0x43, 0x61, 0x66, 0xC3, 0xA9, 0x0A],
expected_substr: "Café",
},
TestCase {
desc: "UTF-8",
bytes: &[0x43, 0x7A, 0x65, 0xC5, 0x9B, 0xC4, 0x87, 0x0A], expected_substr: "Cze",
},
TestCase {
desc: "Windows-1252",
bytes: &[0x43, 0x61, 0x66, 0xE9, 0x0A],
expected_substr: "Café",
},
TestCase {
desc: "Windows-1250",
bytes: &[0xF3, 0xB3, 0xE6, 0x0A],
expected_substr: "ó",
},
];
let temp_dir = TempDir::new().unwrap();
for (i, tc) in test_cases.iter().enumerate() {
let file_path = temp_dir.path().join(format!("test_{}.txt", i));
std::fs::write(&file_path, tc.bytes).unwrap();
let mut harness = EditorTestHarness::new(100, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let encoding_pos = harness
.find_text_on_screen("UTF-16")
.or_else(|| harness.find_text_on_screen("UTF-8"))
.or_else(|| harness.find_text_on_screen("Windows"))
.or_else(|| harness.find_text_on_screen("Latin"))
.or_else(|| harness.find_text_on_screen("GB18030"))
.or_else(|| harness.find_text_on_screen("GBK"))
.or_else(|| harness.find_text_on_screen("Shift"))
.or_else(|| harness.find_text_on_screen("EUC"))
.or_else(|| harness.find_text_on_screen("ASCII"));
let (col, row) = encoding_pos.unwrap_or_else(|| {
panic!(
"Test {} ({}): Could not find encoding indicator",
i, tc.desc
)
});
harness.mouse_click(col, row).unwrap();
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness.type_text("UTF-8").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let _ = harness.wait_until(|h| !h.editor().active_state().buffer.is_modified());
let saved = std::fs::read(&file_path).unwrap();
let utf8_str = std::str::from_utf8(&saved).unwrap_or_else(|e| {
panic!(
"Test {} ({}): File should be valid UTF-8. Error: {}. Bytes: {:?}",
i, tc.desc, e, saved
)
});
assert!(
utf8_str.contains(tc.expected_substr),
"Test {} ({}): Expected '{}' in saved file. Got: {}",
i,
tc.desc,
tc.expected_substr,
utf8_str
);
drop(harness);
}
}
#[test]
fn test_large_file_gbk_encoding_confirmation_prompt() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_gbk.txt");
let mut gbk_bytes = Vec::new();
for _ in 0..60 {
gbk_bytes.extend_from_slice(&[
0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7, 0x0A, ]);
}
assert!(
gbk_bytes.len() >= 500,
"File should be at least 500 bytes (got {})",
gbk_bytes.len()
);
std::fs::write(&file_path, &gbk_bytes).unwrap();
let mut harness = EditorTestHarness::with_config(
100,
30,
fresh::config::Config {
editor: fresh::config::EditorConfig {
large_file_threshold_bytes: 500, ..Default::default()
},
..Default::default()
},
)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text(file_path.to_str().unwrap()).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("requires full load") || screen.contains("GB18030"),
"Should show confirmation prompt for GBK encoding. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert!(
screen_after.contains('你') || screen_after.contains('好'),
"Chinese characters should be visible after loading. Screen:\n{}",
screen_after
);
assert!(
screen_after.contains("GB18030"),
"Status bar should show GB18030 encoding. Screen:\n{}",
screen_after
);
}
#[test]
fn test_large_file_gbk_encoding_cancel() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_gbk_cancel.txt");
let mut gbk_bytes = Vec::new();
for _ in 0..60 {
gbk_bytes.extend_from_slice(&[
0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7, 0x0A, ]);
}
std::fs::write(&file_path, &gbk_bytes).unwrap();
let mut harness = EditorTestHarness::with_config(
100,
30,
fresh::config::Config {
editor: fresh::config::EditorConfig {
large_file_threshold_bytes: 500,
..Default::default()
},
..Default::default()
},
)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text(file_path.to_str().unwrap()).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("requires full load") || screen.contains("GB18030"),
"Should show confirmation prompt. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Char('c'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert!(
screen_after.contains("cancel") || screen_after.contains("Cancel"),
"Should show cancellation message. Screen:\n{}",
screen_after
);
}
#[test]
fn test_large_file_gbk_encoding_change() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_gbk_encoding.txt");
let mut gbk_bytes = Vec::new();
for _ in 0..60 {
gbk_bytes.extend_from_slice(&[
0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7, 0x0A, ]);
}
std::fs::write(&file_path, &gbk_bytes).unwrap();
let mut harness = EditorTestHarness::with_config(
100,
30,
fresh::config::Config {
editor: fresh::config::EditorConfig {
large_file_threshold_bytes: 500,
..Default::default()
},
..Default::default()
},
)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text(file_path.to_str().unwrap()).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("requires full load") || screen.contains("GB18030"),
"Should show confirmation prompt. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Char('e'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert!(
screen_after.contains("Encoding:") || screen_after.contains("UTF-16"),
"Encoding selector should be open. Screen:\n{}",
screen_after
);
}
#[test]
fn test_large_file_shift_jis_encoding_confirmation() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_shift_jis.txt");
let mut sjis_bytes = Vec::new();
for _ in 0..50 {
sjis_bytes.extend_from_slice(&[
0x82, 0xB1, 0x82, 0xF1, 0x82, 0xC9, 0x82, 0xBF, 0x82, 0xCD, 0x0A, ]);
}
assert!(
sjis_bytes.len() >= 500,
"File should be at least 500 bytes (got {})",
sjis_bytes.len()
);
std::fs::write(&file_path, &sjis_bytes).unwrap();
let mut harness = EditorTestHarness::with_config(
100,
30,
fresh::config::Config {
editor: fresh::config::EditorConfig {
large_file_threshold_bytes: 500,
..Default::default()
},
..Default::default()
},
)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text(file_path.to_str().unwrap()).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("requires full load") || screen.contains("Shift-JIS"),
"Should show confirmation prompt for Shift-JIS encoding. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Char('L'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert!(
screen_after.contains('こ')
|| screen_after.contains('ん')
|| screen_after.contains("Shift-JIS"),
"Should show Shift-JIS content or encoding indicator. Screen:\n{}",
screen_after
);
}
#[test]
fn test_large_file_encoding_selector_non_sync_shows_prompt() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_gbk_resync.txt");
let mut gbk_bytes = Vec::new();
for _ in 0..60 {
gbk_bytes.extend_from_slice(&[
0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7, 0x0A, ]);
}
std::fs::write(&file_path, &gbk_bytes).unwrap();
let mut harness = EditorTestHarness::with_config(
100,
30,
fresh::config::Config {
editor: fresh::config::EditorConfig {
large_file_threshold_bytes: 500,
..Default::default()
},
..Default::default()
},
)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text(file_path.to_str().unwrap()).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("requires full load") || screen.contains("GB18030"),
"Should show first confirmation prompt. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Char('e'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
for _ in 0..30 {
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
}
harness.type_text("Shift-JIS").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert!(
screen_after.contains("requires full load") || screen_after.contains("Shift-JIS"),
"Should show confirmation prompt again for Shift-JIS. Screen:\n{}",
screen_after
);
}
#[test]
fn test_large_file_encoding_selector_sync_no_prompt() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_gbk_utf8.txt");
let mut gbk_bytes = Vec::new();
for _ in 0..60 {
gbk_bytes.extend_from_slice(&[
0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7, 0x0A, ]);
}
std::fs::write(&file_path, &gbk_bytes).unwrap();
let mut harness = EditorTestHarness::with_config(
100,
30,
fresh::config::Config {
editor: fresh::config::EditorConfig {
large_file_threshold_bytes: 500,
..Default::default()
},
..Default::default()
},
)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text(file_path.to_str().unwrap()).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("requires full load") || screen.contains("GB18030"),
"Should show first confirmation prompt. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Char('e'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
for _ in 0..30 {
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
}
harness.type_text("UTF-8").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
assert!(
!screen_after.contains("requires full load"),
"Should NOT show confirmation prompt for UTF-8 (synchronizable). Screen:\n{}",
screen_after
);
assert!(
screen_after.contains("UTF-8"),
"File should be opened with UTF-8 encoding. Screen:\n{}",
screen_after
);
}
#[test]
fn test_reload_with_encoding_command() {
let mut harness = EditorTestHarness::with_temp_project(100, 24).unwrap();
let file_path = harness.project_dir().unwrap().join("test_reload.txt");
let latin1_content: Vec<u8> = vec![0x63, 0x61, 0x66, 0xE9, 0x0A]; std::fs::write(&file_path, &latin1_content).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let initial_screen = harness.screen_to_string();
assert!(
initial_screen.contains("Latin-1") || initial_screen.contains("Windows-1252"),
"File should be detected as Latin-1 or Windows-1252. Screen:\n{}",
initial_screen
);
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("Reload with").unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Reload with Encoding");
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Reload with encoding:");
harness
.send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
.unwrap();
harness.type_text("UTF-8").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("UTF-8");
harness.assert_screen_contains("Reloaded with");
}
#[test]
fn test_reload_with_encoding_menu_item() {
let mut harness = EditorTestHarness::with_temp_project(100, 24).unwrap();
let file_path = harness.project_dir().unwrap().join("test_menu.txt");
std::fs::write(&file_path, "Hello World\n").unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('f'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Reload with Encoding...");
}