test(tui): add Loading + JSON expansion unit tests

This commit is contained in:
dailz
2026-04-14 17:38:35 +08:00
parent 06b2a39816
commit 81cc72bd84

View File

@@ -2079,4 +2079,285 @@ plain text line
// Should not panic
app.poll_file_watcher();
}
// ── Loading + JSON expansion tests ─────────────────────────────
#[test]
fn test_tab_toggle_during_loading() {
let content = "line1\n{\"ts\":\"2025\",\"level\":\"ERROR\",\"msg\":\"hello\"}\nline3\n";
let path = make_temp_file(content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading(), "should be in Loading state");
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let tab = KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE);
app.handle_key(tab);
assert!(app.json_format, "Tab should toggle json_format to true during Loading");
assert_eq!(
app.viewport_cache.width, 0,
"Tab should invalidate viewport cache (width==0)"
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_expanded_visual_height() {
let json_line = r#"{"timestamp":"2025-04-14T10:00:00.000Z","level":"INFO","message":"test"}"#;
let content = format!("line1\n{json_line}\nline3\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
let height = app.compute_visual_height(1, 40);
assert!(
height > 1,
"JSON expanded line should have visual_height > 1, got {height}"
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_viewport_contains_cursor() {
let json_line = r#"{"timestamp":"2025-04-14T10:00:00.000Z","level":"INFO","message":"test"}"#;
let mut lines: Vec<String> = (0..50).map(|i| format!("line{i}")).collect();
lines.push(json_line.to_string());
lines.push("end".to_string());
let content = lines.join("\n") + "\n";
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
app.content_height = 24;
app.cursor_line = 50;
app.ensure_viewport_cache(80);
let entries = &app.viewport_cache.entries;
let first = app.viewport_cache.logical_start;
let last_logical = first + entries.len().saturating_sub(1);
assert!(
app.cursor_line >= first && app.cursor_line <= last_logical,
"cursor_line {} should be within viewport range [{}, {}]",
app.cursor_line, first, last_logical
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_scroll_down_line() {
let json_line = r#"{"ts":"2025","level":"ERROR","msg":"hello"}"#;
let content = format!("line1\n{json_line}\nline3\nline4\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
app.content_height = 24;
assert_eq!(app.cursor_line, 0);
app.scroll_down_line();
assert_eq!(
app.cursor_line, 1,
"scroll_down_line should increment cursor_line by 1"
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_scroll_half_page() {
let json_line = r#"{"ts":"2025","level":"ERROR","msg":"hello"}"#;
let content = format!("line1\n{json_line}\nline3\nline4\nline5\nline6\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
app.content_height = 24;
// Should not panic
app.scroll_down_half_page();
let total = app.total_lines();
assert!(
app.cursor_line < total,
"cursor_line {} should be < total_lines {}",
app.cursor_line, total
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_scroll_to_bottom() {
let json_line = r#"{"ts":"2025","level":"ERROR","msg":"hello"}"#;
let content = format!("line1\n{json_line}\nline3\nline4\nline5\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
app.content_height = 24;
// Should not panic
app.scroll_to_bottom();
assert_eq!(
app.cursor_line,
app.total_lines() - 1,
"scroll_to_bottom should set cursor_line to last line"
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_to_ready_preserves_json_format() {
let json_line = r#"{"timestamp":"2025-04-14T10:00:00.000Z","level":"INFO","message":"test"}"#;
let content = format!("line1\n{json_line}\nline3\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading(), "should start in Loading state");
// load_file resets json_format to false
app.json_format = true;
app.content_height = 24;
// Manually transition Loading → Ready
let old_state = std::mem::replace(&mut app.loading_state, AppLoadingState::Empty);
if let AppLoadingState::Loading { reader, .. } = old_state {
app.loading_state = AppLoadingState::Ready { reader };
}
app.viewport_cache.invalidate();
assert!(!app.is_loading(), "should now be in Ready state");
app.ensure_viewport_cache(80);
assert!(
app.json_format,
"json_format should remain true after Loading→Ready transition"
);
let has_expanded = app.viewport_cache.entries.iter().any(|e| e.visual_height > 1);
assert!(
has_expanded,
"viewport should contain entries with visual_height > 1 (JSON expanded)"
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_tab_toggle_off_during_loading() {
let json_line = r#"{"ts":"2025","level":"ERROR","msg":"hello"}"#;
let content = format!("line1\n{json_line}\nline3\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let tab = KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE);
// Tab ON
app.handle_key(tab);
assert!(app.json_format, "first Tab should set json_format=true");
// Tab OFF
app.handle_key(tab);
assert!(!app.json_format, "second Tab should set json_format=false");
app.content_height = 24;
app.ensure_viewport_cache(80);
for (i, entry) in app.viewport_cache.entries.iter().enumerate() {
assert_eq!(
entry.visual_height, 1,
"entry {} should have height 1 with json_format off",
i
);
}
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_empty_line() {
let content = "line1\n\nline3\n";
let path = make_temp_file(content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
let height = app.compute_visual_height(1, 80);
assert_eq!(
height, 1,
"empty line should have visual_height 1, got {height}"
);
});
cleanup(&path);
assert!(result.is_ok());
}
#[test]
fn test_loading_json_deep_nested() {
let deep_json = r#"{"a":{"b":{"c":{"d":"value"}}}}"#;
let content = format!("line1\n{deep_json}\nline3\n");
let path = make_temp_file(&content);
let result = std::panic::catch_unwind(|| {
let mut app = App::new();
app.load_file(path.to_str().unwrap()).unwrap();
assert!(app.is_loading());
app.json_format = true;
app.content_height = 24;
app.ensure_viewport_cache(20);
let json_height = app.compute_visual_height(1, 20);
assert!(
json_height > 1,
"deep nested JSON at width 20 should have visual_height > 1, got {json_height}"
);
let total_rows: usize = app.viewport_cache.entries.iter().map(|e| e.visual_height).sum();
assert!(
total_rows <= app.content_height as usize + 2,
"viewport visual rows ({total_rows}) should not wildly exceed content_height ({})",
app.content_height
);
});
cleanup(&path);
assert!(result.is_ok());
}
}