use crate::common::harness::{EditorTestHarness, HarnessOptions};
use crossterm::event::{KeyCode, KeyModifiers};
use fresh::config::Config;
use fresh::config_io::DirectoryContext;
use ratatui::style::Color;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_default_theme_is_dark() {
let harness = EditorTestHarness::new(80, 24).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "high-contrast");
}
#[test]
fn test_theme_loading_from_config_dark() {
let config = Config {
theme: "dark".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "dark");
assert_eq!(theme.editor_bg, Color::Rgb(30, 30, 30));
assert_eq!(theme.editor_fg, Color::Rgb(212, 212, 212));
assert_eq!(theme.tab_active_fg, Color::Yellow);
assert_eq!(theme.tab_active_bg, Color::Blue);
}
#[test]
fn test_theme_loading_from_config_light() {
let config = Config {
theme: "light".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "light");
assert_eq!(theme.editor_bg, Color::Rgb(255, 255, 255));
assert_eq!(theme.editor_fg, Color::Rgb(0, 0, 0));
assert_eq!(theme.tab_active_fg, Color::Rgb(40, 40, 40));
assert_eq!(theme.tab_active_bg, Color::Rgb(255, 255, 255));
}
#[test]
fn test_theme_loading_from_config_high_contrast() {
let config = Config {
theme: "high-contrast".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "high-contrast");
assert_eq!(theme.editor_bg, Color::Black);
assert_eq!(theme.editor_fg, Color::White);
assert_eq!(theme.cursor, Color::White);
assert_eq!(theme.tab_active_fg, Color::Black);
assert_eq!(theme.tab_active_bg, Color::Yellow);
}
#[test]
fn test_invalid_theme_falls_back_to_default() {
let config = Config {
theme: "nonexistent-theme".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "high-contrast");
}
#[test]
fn test_theme_renders_with_correct_tab_colors() {
let config = Config {
theme: "dark".into(),
..Default::default()
};
let mut harness = EditorTestHarness::with_config(80, 24, config).unwrap();
harness.render().unwrap();
let theme = harness.editor().theme();
if let Some(style) = harness.get_cell_style(1, 1) {
assert_eq!(style.fg, Some(theme.tab_active_fg));
assert_eq!(style.bg, Some(theme.tab_active_bg));
}
}
#[test]
fn test_theme_renders_with_correct_status_bar_colors() {
let config = Config {
theme: "dark".into(),
..Default::default()
};
let mut harness = EditorTestHarness::with_config(80, 24, config).unwrap();
harness.render().unwrap();
let theme = harness.editor().theme();
if let Some(style) = harness.get_cell_style(1, 23) {
assert!(
style.bg == Some(theme.status_bar_bg) || style.bg.is_some(),
"Status bar should have a background color set, got: {:?}",
style.bg
);
}
}
#[test]
fn test_light_theme_renders_differently_than_dark() {
let dark_config = Config {
theme: "dark".into(),
..Default::default()
};
let light_config = Config {
theme: "light".into(),
..Default::default()
};
let mut dark_harness = EditorTestHarness::with_config(80, 24, dark_config).unwrap();
let mut light_harness = EditorTestHarness::with_config(80, 24, light_config).unwrap();
dark_harness.render().unwrap();
light_harness.render().unwrap();
let dark_style = dark_harness.get_cell_style(1, 1);
let light_style = light_harness.get_cell_style(1, 1);
assert_ne!(
dark_style, light_style,
"Dark and light themes should render with different colors"
);
}
#[test]
fn test_theme_diagnostic_colors() {
let config = Config {
theme: "dark".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.diagnostic_error_fg, Color::Red);
assert_eq!(theme.diagnostic_error_bg, Color::Rgb(60, 20, 20));
assert_eq!(theme.diagnostic_warning_fg, Color::Yellow);
assert_eq!(theme.diagnostic_warning_bg, Color::Rgb(60, 50, 0));
assert_eq!(theme.diagnostic_info_fg, Color::Blue);
assert_eq!(theme.diagnostic_info_bg, Color::Rgb(0, 30, 60));
}
#[test]
fn test_theme_syntax_highlighting_colors() {
let config = Config {
theme: "dark".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.syntax_keyword, Color::Rgb(86, 156, 214));
assert_eq!(theme.syntax_string, Color::Rgb(206, 145, 120));
assert_eq!(theme.syntax_comment, Color::Rgb(106, 153, 85));
assert_eq!(theme.syntax_function, Color::Rgb(220, 220, 170));
assert_eq!(theme.syntax_type, Color::Rgb(78, 201, 176));
assert_eq!(theme.syntax_variable, Color::Rgb(156, 220, 254));
}
#[test]
fn test_all_available_themes_can_be_loaded() {
let themes = vec!["dark", "light", "high-contrast"];
for theme_name in themes {
let config = Config {
theme: theme_name.into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(
theme.name, theme_name,
"Theme '{theme_name}' should load correctly"
);
}
}
#[test]
fn test_theme_selection_colors() {
let dark_config = Config {
theme: "dark".into(),
..Default::default()
};
let light_config = Config {
theme: "light".into(),
..Default::default()
};
let dark_harness = EditorTestHarness::with_config(80, 24, dark_config).unwrap();
let light_harness = EditorTestHarness::with_config(80, 24, light_config).unwrap();
let dark_theme = dark_harness.editor().theme();
let light_theme = light_harness.editor().theme();
assert_ne!(dark_theme.selection_bg, light_theme.selection_bg);
assert_eq!(dark_theme.selection_bg, Color::Rgb(38, 79, 120));
assert_eq!(light_theme.selection_bg, Color::Rgb(173, 214, 255));
}
#[test]
fn test_theme_popup_colors() {
let config = Config {
theme: "dark".into(),
..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.popup_border_fg, Color::Gray);
assert_eq!(theme.popup_bg, Color::Rgb(30, 30, 30));
assert_eq!(theme.popup_selection_bg, Color::Rgb(58, 79, 120));
assert_eq!(theme.popup_text_fg, Color::White);
}
#[test]
fn test_case_insensitive_theme_name() {
let config = Config {
theme: "HIGH-CONTRAST".into(), ..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "high-contrast");
}
#[test]
fn test_theme_with_underscore_variant() {
let config = Config {
theme: "high_contrast".into(), ..Default::default()
};
let harness = EditorTestHarness::with_config(80, 24, config).unwrap();
let theme = harness.editor().theme();
assert_eq!(theme.name, "high-contrast");
}
fn custom_catppuccin_theme_json() -> &'static str {
r#"{
"name": "Catppuccin Mocha",
"editor": { "bg": [30, 30, 46] },
"ui": {},
"search": {},
"diagnostic": {},
"syntax": {}
}"#
}
#[test]
fn test_issue_1001_theme_persists_after_restart_with_name_mismatch() {
let temp_dir = TempDir::new().unwrap();
let dir_context = DirectoryContext::for_testing(temp_dir.path());
let themes_dir = temp_dir.path().join("config").join("themes");
fs::create_dir_all(&themes_dir).unwrap();
fs::write(
themes_dir.join("catppuccin-mocha.json"),
custom_catppuccin_theme_json(),
)
.unwrap();
let project_root = temp_dir.path().join("project_root");
let plugins_dir = project_root.join("plugins");
fs::create_dir_all(&plugins_dir).unwrap();
let mut harness = EditorTestHarness::create(
100,
40,
HarnessOptions::new()
.with_working_dir(project_root.clone())
.with_shared_dir_context(dir_context.clone())
.without_empty_plugins_dir(),
)
.unwrap();
harness.render().unwrap();
let default_style = harness.get_cell_style(5, 3);
let default_bg = default_style.and_then(|s| s.bg);
assert_eq!(
default_bg,
Some(Color::Black),
"Editor should start with high-contrast theme (black background)"
);
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.wait_for_prompt().unwrap();
harness.type_text("Select Theme").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.wait_for_screen_contains("Select theme").unwrap();
for _ in 0..20 {
harness
.send_key(KeyCode::Backspace, KeyModifiers::NONE)
.unwrap();
}
harness.type_text("catppuccin").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.wait_for_prompt_closed().unwrap();
harness.render().unwrap();
let catppuccin_bg = Color::Rgb(30, 30, 46);
let applied_style = harness.get_cell_style(5, 3);
let applied_bg = applied_style.and_then(|s| s.bg);
assert_eq!(
applied_bg,
Some(catppuccin_bg),
"After selection, editor should render with catppuccin-mocha background (30,30,46), got {:?}",
applied_bg
);
let config_path = temp_dir.path().join("config").join("config.json");
let saved_config = fs::read_to_string(&config_path).unwrap();
let saved_json: serde_json::Value = serde_json::from_str(&saved_config).unwrap();
let saved_theme = saved_json
.get("theme")
.and_then(|t| t.as_str())
.unwrap_or("");
assert_eq!(
saved_theme, "catppuccin-mocha",
"BUG #1001: Config should contain normalized theme name 'catppuccin-mocha', \
not the JSON name field. Got: '{}'. Config content: {}",
saved_theme, saved_config
);
drop(harness);
let restart_config_str = fs::read_to_string(&config_path).unwrap();
let restart_json: serde_json::Value = serde_json::from_str(&restart_config_str).unwrap();
let restart_theme_name = restart_json
.get("theme")
.and_then(|t| t.as_str())
.unwrap_or("high-contrast");
let mut harness2 = EditorTestHarness::create(
100,
40,
HarnessOptions::new()
.with_config(Config {
theme: restart_theme_name.into(),
..Default::default()
})
.with_working_dir(project_root)
.with_shared_dir_context(dir_context)
.without_empty_plugins_dir(),
)
.unwrap();
harness2.render().unwrap();
let restarted_style = harness2.get_cell_style(5, 3);
let restarted_bg = restarted_style.and_then(|s| s.bg);
assert_eq!(
restarted_bg,
Some(catppuccin_bg),
"BUG #1001: After restart, editor should render with catppuccin-mocha background (30,30,46). \
Got {:?} — theme was likely not found and fell back to high-contrast (black). \
Config persisted theme as '{}', config file: {}",
restarted_bg,
restart_theme_name,
restart_config_str
);
drop(temp_dir);
}
#[test]
fn test_issue_1001_config_with_spaces_in_theme_name_loads_correctly() {
let temp_dir = TempDir::new().unwrap();
let dir_context = DirectoryContext::for_testing(temp_dir.path());
let themes_dir = temp_dir.path().join("config").join("themes");
fs::create_dir_all(&themes_dir).unwrap();
fs::write(
themes_dir.join("catppuccin-mocha.json"),
custom_catppuccin_theme_json(),
)
.unwrap();
let project_root = temp_dir.path().join("project_root");
let plugins_dir = project_root.join("plugins");
fs::create_dir_all(&plugins_dir).unwrap();
let mut harness = EditorTestHarness::create(
100,
40,
HarnessOptions::new()
.with_config(Config {
theme: "Catppuccin Mocha".into(),
..Default::default()
})
.with_working_dir(project_root)
.with_shared_dir_context(dir_context)
.without_empty_plugins_dir(),
)
.unwrap();
harness.render().unwrap();
let catppuccin_bg = Color::Rgb(30, 30, 46);
let style = harness.get_cell_style(5, 3);
let bg = style.and_then(|s| s.bg);
assert_eq!(
bg,
Some(catppuccin_bg),
"BUG #1001: Config has 'Catppuccin Mocha' (with spaces). Normalization should \
convert this to 'catppuccin-mocha' and find the theme file. Instead got bg={:?}, \
which suggests fallback to high-contrast (black).",
bg
);
drop(temp_dir);
}