#![cfg(feature = "tui")]
use periplon_sdk::tui::state::EditorState;
use periplon_sdk::tui::theme::Theme;
use periplon_sdk::tui::views::editor::{EditorMode, ValidationFeedback};
use ratatui::backend::TestBackend;
use ratatui::Terminal;
use std::path::PathBuf;
fn create_test_terminal(width: u16, height: u16) -> Terminal<TestBackend> {
let backend = TestBackend::new(width, height);
Terminal::new(backend).unwrap()
}
fn render_editor(
state: &EditorState,
feedback: &ValidationFeedback,
theme: &Theme,
width: u16,
height: u16,
) -> Terminal<TestBackend> {
let mut terminal = create_test_terminal(width, height);
terminal
.draw(|frame| {
let area = frame.area();
periplon_sdk::tui::views::editor::render(frame, area, state, feedback, theme);
})
.unwrap();
terminal
}
fn buffer_contains_text(terminal: &Terminal<TestBackend>, text: &str) -> bool {
let buffer = terminal.backend().buffer();
let content = buffer.content();
let visible: String = content
.iter()
.map(|cell| cell.symbol())
.collect::<Vec<_>>()
.join("");
visible.contains(text)
}
fn count_border_chars(terminal: &Terminal<TestBackend>) -> usize {
let buffer = terminal.backend().buffer();
buffer
.content()
.iter()
.filter(|cell| {
let symbol = cell.symbol();
matches!(
symbol,
"─" | "│" | "┌" | "┐" | "└" | "┘" | "├" | "┤" | "┬" | "┴" | "┼"
)
})
.count()
}
#[test]
fn test_editor_basic_rendering() {
let mut state = EditorState::new();
state.content = "name: test\nversion: 1.0.0".to_string();
state.file_path = Some(PathBuf::from("test.yaml"));
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
assert!(buffer_contains_text(&terminal, "test.yaml"));
assert!(buffer_contains_text(&terminal, "name"));
}
#[test]
fn test_editor_empty_content() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
assert!(buffer_contains_text(&terminal, "<new workflow>"));
}
#[test]
fn test_editor_with_file_path() {
let mut state = EditorState::new();
state.file_path = Some(PathBuf::from("/path/to/my-workflow.yaml"));
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "my-workflow.yaml"));
}
#[test]
fn test_header_shows_mode() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "Text"));
state.mode = EditorMode::Form;
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "Form"));
}
#[test]
fn test_header_shows_validation_status_valid() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "✓"));
assert!(buffer_contains_text(&terminal, "Valid"));
}
#[test]
fn test_header_shows_validation_status_invalid() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
feedback
.errors
.push((1, "Missing required field".to_string()));
feedback.errors.push((5, "Invalid syntax".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "✗"));
assert!(buffer_contains_text(&terminal, "2"));
assert!(buffer_contains_text(&terminal, "errors"));
}
#[test]
fn test_header_shows_modified_indicator() {
let mut state = EditorState::new();
state.modified = false;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
let buffer_without_modified = buffer_contains_text(&terminal, "[Modified]");
state.modified = true;
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
let buffer_with_modified = buffer_contains_text(&terminal, "[Modified]");
assert!(!buffer_without_modified);
assert!(buffer_with_modified);
}
#[test]
fn test_text_editor_shows_line_numbers() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
state.content = "line 1\nline 2\nline 3".to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "│"));
}
#[test]
fn test_text_editor_yaml_syntax_highlighting() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
state.content = r#"# Comment
name: "Test Workflow"
version: "1.0.0"
agents:
- test_agent
tasks:
test_task:
description: "Task"
"#
.to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "name"));
assert!(buffer_contains_text(&terminal, "version"));
assert!(buffer_contains_text(&terminal, "agents"));
}
#[test]
fn test_text_editor_shows_yaml_title() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "YAML Editor"));
}
#[test]
fn test_text_editor_inline_error_markers() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
state.content = "line 1\nline 2\nline 3".to_string();
let mut feedback = ValidationFeedback::new();
feedback.errors.push((2, "Error on line 2".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "❌"));
}
#[test]
fn test_text_editor_inline_warning_markers() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
state.content = "line 1\nline 2\nline 3".to_string();
let mut feedback = ValidationFeedback::new();
feedback.warnings.push((1, "Warning on line 1".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "⚠️") || buffer_contains_text(&terminal, "⚠"));
}
#[test]
fn test_form_editor_shows_title() {
let mut state = EditorState::new();
state.mode = EditorMode::Form;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "Form Editor"));
}
#[test]
fn test_form_editor_with_valid_workflow() {
let mut state = EditorState::new();
state.mode = EditorMode::Form;
state.content = r#"
name: "Test Workflow"
version: "1.0.0"
agents:
researcher:
description: "Research agent"
model: "claude-sonnet-4-5"
tasks:
task1:
description: "Test task"
agent: "researcher"
"#
.to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 30);
assert!(buffer_contains_text(&terminal, "Workflow Metadata"));
assert!(buffer_contains_text(&terminal, "Agents"));
let _has_tasks = buffer_contains_text(&terminal, "Tasks");
}
#[test]
fn test_form_editor_with_invalid_yaml() {
let mut state = EditorState::new();
state.mode = EditorMode::Form;
state.content = "invalid: yaml: syntax: here".to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "Unable to parse"));
assert!(buffer_contains_text(&terminal, "Text mode"));
}
#[test]
fn test_validation_status_bar_no_errors() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "✓"));
assert!(buffer_contains_text(&terminal, "Valid"));
}
#[test]
fn test_validation_status_bar_with_errors() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
feedback.errors.push((1, "Missing name field".to_string()));
feedback
.errors
.push((5, "Invalid agent reference".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "✗"));
assert!(buffer_contains_text(&terminal, "2"));
assert!(buffer_contains_text(&terminal, "error"));
}
#[test]
fn test_validation_status_bar_with_warnings() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
feedback
.warnings
.push((3, "Consider adding description".to_string()));
feedback.warnings.push((10, "Unused variable".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "2"));
assert!(buffer_contains_text(&terminal, "warning"));
}
#[test]
fn test_validation_status_bar_mixed_errors_and_warnings() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
feedback.errors.push((1, "Error message".to_string()));
feedback.warnings.push((2, "Warning message".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "error"));
assert!(buffer_contains_text(&terminal, "warning"));
}
#[test]
fn test_validation_status_bar_keybinding() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "v:"));
assert!(buffer_contains_text(&terminal, "Valid"));
}
#[test]
fn test_validation_modal_not_shown_when_collapsed() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
feedback.errors.push((1, "Error message".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
let has_details = buffer_contains_text(&terminal, "Validation Details");
assert!(!has_details); }
#[test]
fn test_validation_modal_shown_when_expanded() {
let mut state = EditorState::new();
state.validation_expanded = true;
let mut feedback = ValidationFeedback::new();
feedback.errors.push((1, "Missing name field".to_string()));
feedback
.errors
.push((5, "Invalid agent reference".to_string()));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 30);
assert!(buffer_contains_text(&terminal, "Validation Details"));
assert!(buffer_contains_text(&terminal, "Missing name"));
assert!(buffer_contains_text(&terminal, "Invalid agent"));
}
#[test]
fn test_status_bar_text_mode() {
let mut state = EditorState::new();
state.mode = EditorMode::Text;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "^S"));
assert!(buffer_contains_text(&terminal, "Save"));
assert!(buffer_contains_text(&terminal, "^R"));
assert!(buffer_contains_text(&terminal, "Run"));
assert!(buffer_contains_text(&terminal, "v:"));
assert!(buffer_contains_text(&terminal, "Valid"));
assert!(buffer_contains_text(&terminal, "Tab"));
assert!(buffer_contains_text(&terminal, "Form"));
assert!(buffer_contains_text(&terminal, "Esc"));
assert!(buffer_contains_text(&terminal, "Back"));
}
#[test]
fn test_status_bar_form_mode() {
let mut state = EditorState::new();
state.mode = EditorMode::Form;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "Tab"));
assert!(buffer_contains_text(&terminal, "Text"));
assert!(buffer_contains_text(&terminal, "^S"));
assert!(buffer_contains_text(&terminal, "Save"));
assert!(buffer_contains_text(&terminal, "v:"));
assert!(buffer_contains_text(&terminal, "Valid"));
assert!(buffer_contains_text(&terminal, "Esc"));
assert!(buffer_contains_text(&terminal, "Back"));
}
#[test]
fn test_status_bar_modified_indicator() {
let mut state = EditorState::new();
state.modified = true;
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "[Modified]"));
}
#[test]
fn test_editor_layout_structure() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 30);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_minimum_size() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 60, 20);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_large_terminal() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 200, 60);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_multiline_content() {
let mut state = EditorState::new();
state.content = "line 1\nline 2\nline 3\nline 4\nline 5".to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "│"));
}
#[test]
fn test_editor_long_lines() {
let mut state = EditorState::new();
state.content = "a".repeat(200);
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_special_characters() {
let mut state = EditorState::new();
state.content = "name: 日本語\ntitle: \"Test 🎉\"\nversion: ñ-ü-ö".to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_empty_lines() {
let mut state = EditorState::new();
state.content = "line 1\n\n\nline 4".to_string();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_with_different_themes() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let themes = vec![
Theme::default(),
Theme::light(),
Theme::monokai(),
Theme::solarized(),
];
for theme in themes {
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
}
#[test]
fn test_editor_cursor_at_beginning() {
let mut state = EditorState::new();
state.content = "test content".to_string();
state.cursor = (0, 0);
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_cursor_at_end() {
let mut state = EditorState::new();
state.content = "line 1\nline 2".to_string();
state.cursor = (1, 6);
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_cursor_line_highlighting() {
let mut state = EditorState::new();
state.content = "line 1\nline 2\nline 3".to_string();
state.cursor = (1, 0);
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_scroll_offset() {
let mut state = EditorState::new();
state.content = (0..50)
.map(|i| format!("line {}", i))
.collect::<Vec<_>>()
.join("\n");
state.scroll = (10, 0);
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_scrollbar_with_long_content() {
let mut state = EditorState::new();
state.content = (0..100)
.map(|i| format!("line {}", i))
.collect::<Vec<_>>()
.join("\n");
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "↑") || buffer_contains_text(&terminal, "↓"));
}
#[test]
fn test_validation_feedback_helper_methods() {
let mut feedback = ValidationFeedback::new();
assert!(feedback.is_valid());
assert_eq!(feedback.error_count(), 0);
assert_eq!(feedback.warning_count(), 0);
feedback.errors.push((1, "Error".to_string()));
assert!(!feedback.is_valid());
assert_eq!(feedback.error_count(), 1);
feedback.warnings.push((2, "Warning".to_string()));
assert_eq!(feedback.warning_count(), 1);
}
#[test]
fn test_validation_feedback_timestamp() {
let mut feedback = ValidationFeedback::new();
assert!(feedback.validated_at.is_none());
feedback.validated_at = Some(std::time::Instant::now());
assert!(feedback.validated_at.is_some());
}
#[test]
fn test_editor_very_long_error_message() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
feedback.errors.push((
1,
"This is a very long error message that might exceed the available width and should be handled gracefully by the rendering system".to_string(),
));
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(count_border_chars(&terminal) > 0);
}
#[test]
fn test_editor_many_errors() {
let state = EditorState::new();
let mut feedback = ValidationFeedback::new();
for i in 1..=50 {
feedback.errors.push((i, format!("Error {}", i)));
}
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "50"));
assert!(buffer_contains_text(&terminal, "error"));
}
#[test]
fn test_editor_no_file_path() {
let state = EditorState::new();
let feedback = ValidationFeedback::new();
let theme = Theme::default();
let terminal = render_editor(&state, &feedback, &theme, 80, 24);
assert!(buffer_contains_text(&terminal, "<new workflow>"));
}
#[test]
fn test_editor_mode_enum() {
assert_ne!(EditorMode::Text, EditorMode::Form);
}