use std::fs;
use std::path::PathBuf;
use calcli::config::Config;
use calcli::config::loader::config_from_str;
use calcli::domain::evaluator::MevalEvaluator;
use calcli::domain::history::{History, HistoryEntry};
use calcli::domain::quantity::Quantity;
use calcli::domain::variables::VariableStore;
use calcli::services::CalcService;
use calcli::storage::{PersistedState, StateRepository, TomlStateRepository};
use calcli::theme::{Color, GlyphVariant};
fn scratch(tag: &str) -> PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("calcli-legacy-{tag}-{}.toml", std::process::id()));
path
}
fn load_fixture(name: &str, tag: &str) -> PersistedState {
let source = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/data")
.join(name);
let path = scratch(tag);
fs::copy(&source, &path).expect("fixture is readable");
let state = TomlStateRepository::new(path.clone())
.load()
.unwrap_or_else(|error| panic!("{name} must load: {error}"));
let _ = fs::remove_file(&path);
state
}
const LEGACY_CONFIG: &str = include_str!("data/config-0.2.toml");
#[test]
fn a_0_2_config_file_is_rejected_by_name_for_its_zebra_key() {
let error = config_from_str(LEGACY_CONFIG)
.expect_err("history_zebra no longer exists");
let cause = std::error::Error::source(&error)
.expect("a parse failure carries the TOML error")
.to_string();
assert!(
cause.contains("history_zebra"),
"the message must name the offending key: {cause}",
);
}
#[test]
fn a_0_2_config_file_still_loads_with_its_colours_mapped() {
let text = LEGACY_CONFIG.replace("history_zebra = false\n", "");
let config = config_from_str(&text).expect("the 0.2 config must load");
assert_eq!(config.decimals, 3);
assert_eq!(config.max_history, 500);
assert_eq!(config.appearance.glyphs, GlyphVariant::Unicode);
assert!(config.live_feedback);
let palette = config.palette();
assert_eq!(palette.accent, Color::hex("#6dd0ff"));
assert_eq!(palette.footer, Color::hex("#252525"));
assert_eq!(palette.border, Color::hex("#3e3e3e"));
assert_eq!(palette.panel, Config::default().palette().panel);
assert_eq!(config.highlight.function, Color::hex("#78c2b3"));
assert_eq!(config.highlight.unit, Color::hex("#ff79c6"));
}
#[test]
fn a_0_2_state_file_still_loads_with_its_history_and_settings() {
let state = load_fixture("state-0.2.toml", "state02");
let settings = state.settings.expect("the settings survive");
assert_eq!(settings.decimal_separator, ",");
assert!(settings.trim_trailing_zeros);
assert!(state.history.len() >= 3, "the history survives");
assert_eq!(state.history[0].input, "1+3");
assert_eq!(state.history[0].value, Some(4.0));
assert!(state.history.iter().any(|entry| entry.value.is_none()));
assert_eq!(state.ui.active_view, 0);
assert_eq!(state.ui.hints_visible, None);
}
#[test]
fn a_pre_units_state_file_still_loads_its_bare_variables() {
let state = load_fixture("state-pre-units.toml", "preunits");
assert_eq!(state.variables.len(), 2);
assert_eq!(state.variables["g"].value, 9.81);
assert_eq!(state.variables["g"].unit, None);
assert_eq!(state.variables["r"].value, 2.0);
assert_eq!(state.history.len(), 3);
}
#[test]
fn a_pre_units_session_recomputes_into_the_same_values() {
let state = load_fixture("state-pre-units.toml", "replay");
let variables = VariableStore::from_pairs(state.variables.iter().map(
|(name, stored)| {
let quantity =
Quantity::from_persisted(stored.value, stored.unit.as_deref());
(name.clone(), quantity)
},
));
let entries: Vec<HistoryEntry> = state
.history
.iter()
.map(|entry| HistoryEntry {
input: entry.input.clone(),
value: entry
.value
.map(|v| Quantity::from_persisted(v, entry.unit.as_deref())),
error: None,
})
.collect();
let mut service = CalcService::new(
Box::new(MevalEvaluator::new()),
Config::default().format_settings(),
History::from_entries(entries, 500),
variables,
);
service.recompute_all();
let value_at = |index: usize| {
service.history().entries()[index]
.value
.as_ref()
.map(Quantity::display_value)
};
assert_eq!(value_at(0), Some(std::f64::consts::TAU * 2.0));
assert_eq!(value_at(2), Some(9.81 * 3.0));
}
#[test]
fn a_legacy_state_file_is_rewritten_in_the_current_shape() {
let state = load_fixture("state-pre-units.toml", "rewrite");
let path = scratch("rewrite-out");
let repository = TomlStateRepository::new(path.clone());
repository.save(&state).expect("save");
let written = fs::read_to_string(&path).expect("read back");
let reloaded = repository.load().expect("reload");
let _ = fs::remove_file(&path);
assert!(written.contains("[variables.g]"), "{written}");
assert_eq!(reloaded.variables["g"].value, 9.81);
assert_eq!(reloaded.history.len(), 3);
}