test(tahuantinsuyu): tests de integración del Shell con TestAppContext
Cubre los wiring points del binario que las unit tests por-crate no
ven: construcción end-to-end de Shell, selección de carta, derivación
de PipelineRequests y NatalOptions desde module_configs, y roundtrip
de layout via la tabla settings.
- `gpui` con `test-support` en dev-dependencies.
- 5 tests en `shell::tests`:
* `shell_constructs_smoke` — instancia Shell con store in-memory
sin panic. Cubre cableado de suscripciones (tree/panel/canvas
+ 2 splitters) y arranque del background loop del broker.
* `select_chart_updates_current` — apply_selection(Chart(id))
puebla `current_chart` y avanza `render_seq`.
* `module_toggles_produce_requests` — al habilitar 3 módulos
overlay, `build_requests` devuelve esos 3 PipelineRequest en
orden; deshabilitar uno lo remueve.
* `natal_options_read_from_configs` — orb_multiplier, show_minors,
show_dignities se leen correctamente desde module_configs["natal"].
* `split_flex_round_trip_via_store` — load/save_split_flex con
settings, incluyendo defaults para valores corruptos o ≤0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1016,3 +1016,178 @@ impl Render for Shell {
|
||||
.child(body)
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Tests de integración del Shell
|
||||
// =====================================================================
|
||||
//
|
||||
// Cubren los caminos que combinan lógica del shell con persistencia y
|
||||
// el bridge real de eternal. Los tests puramente unitarios de cada
|
||||
// crate (engine, store, modules) viven en sus respectivos `tests`
|
||||
// modules; acá testeamos los wiring points del binario.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use tahuantinsuyu_model::{
|
||||
ChartKind, ContactId, StoredBirthData, StoredChartConfig,
|
||||
};
|
||||
|
||||
fn sample_chart_for(_contact_id: ContactId) -> (StoredBirthData, StoredChartConfig) {
|
||||
(
|
||||
StoredBirthData {
|
||||
year: 1987,
|
||||
month: 3,
|
||||
day: 14,
|
||||
hour: 5,
|
||||
minute: 22,
|
||||
second: 0.0,
|
||||
tz_offset_minutes: -240,
|
||||
latitude_deg: 10.4806,
|
||||
longitude_deg: -66.9036,
|
||||
altitude_m: 900.0,
|
||||
time_certainty: Default::default(),
|
||||
subject_name: Some("Sergio".into()),
|
||||
birthplace_label: Some("Caracas".into()),
|
||||
},
|
||||
StoredChartConfig::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Smoke test: el Shell se construye sin panic con una store
|
||||
/// in-memory. Cubre que las suscripciones cross-widget (tree, panel,
|
||||
/// canvas, ambos splitters) se cablean sin colisiones y que el
|
||||
/// background loop del brahman status arranca limpio.
|
||||
#[gpui::test]
|
||||
fn shell_constructs_smoke(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::install_default(cx);
|
||||
let store = Store::in_memory().expect("in-memory store");
|
||||
let _shell = cx.new(|cx| Shell::new(store, cx));
|
||||
// Si llegamos acá sin panic, el cableado funciona.
|
||||
});
|
||||
}
|
||||
|
||||
/// La selección de una carta vía `apply_selection` (mismo pathway
|
||||
/// que dispara el TreeEvent) puebla `current_chart` y arranca un
|
||||
/// compute. El render asíncrono se resuelve después; verificamos
|
||||
/// solo los efectos sincrónicos: chart cargada y `render_seq`
|
||||
/// avanzado.
|
||||
#[gpui::test]
|
||||
fn select_chart_updates_current(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::install_default(cx);
|
||||
let store = Store::in_memory().expect("store");
|
||||
let group = store.create_group(None, "Test", None).unwrap();
|
||||
let contact = store
|
||||
.create_contact(Some(group.id), "Subject", None)
|
||||
.unwrap();
|
||||
let (birth, config) = sample_chart_for(contact.id);
|
||||
let chart = store
|
||||
.create_chart(contact.id, ChartKind::Natal, "Natal", &birth, &config, None)
|
||||
.unwrap();
|
||||
|
||||
let shell = cx.new(|cx| Shell::new(store, cx));
|
||||
shell.update(cx, |s, cx| {
|
||||
s.apply_selection(TreeSelection::Chart(chart.id), cx);
|
||||
});
|
||||
shell.read_with(cx, |s, _| {
|
||||
let cur = s.current_chart.as_ref().expect("current_chart set");
|
||||
assert_eq!(cur.id, chart.id);
|
||||
assert_eq!(cur.label, "Natal");
|
||||
assert!(s.render_seq >= 1, "render_seq debió avanzar al menos a 1");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Toggleando un módulo overlay vía `module_configs` directamente
|
||||
/// (simulando el efecto de un `PanelEvent::ControlChanged`), la
|
||||
/// función `build_requests` debe reflejar el cambio.
|
||||
#[gpui::test]
|
||||
fn module_toggles_produce_requests(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::install_default(cx);
|
||||
let store = Store::in_memory().expect("store");
|
||||
let shell = cx.new(|cx| Shell::new(store, cx));
|
||||
|
||||
shell.update(cx, |s, _cx| {
|
||||
// Sin módulos activos → no hay requests.
|
||||
assert!(s.build_requests().is_empty());
|
||||
|
||||
set_module_enabled(&mut s.module_configs, "transit", true);
|
||||
set_module_enabled(&mut s.module_configs, "midpoints", true);
|
||||
set_module_enabled(&mut s.module_configs, "uranian", true);
|
||||
|
||||
let reqs = s.build_requests();
|
||||
assert_eq!(reqs.len(), 3);
|
||||
assert!(matches!(reqs[0], PipelineRequest::Transit));
|
||||
assert!(matches!(reqs[1], PipelineRequest::Midpoints));
|
||||
assert!(matches!(reqs[2], PipelineRequest::Uranian));
|
||||
|
||||
set_module_enabled(&mut s.module_configs, "transit", false);
|
||||
let reqs = s.build_requests();
|
||||
assert_eq!(reqs.len(), 2);
|
||||
assert!(!reqs
|
||||
.iter()
|
||||
.any(|r| matches!(r, PipelineRequest::Transit)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// `NatalOptions` derivados de `module_configs["natal"]` deben
|
||||
/// respetar orb_multiplier, show_minors y show_dignities cuando los
|
||||
/// hay, y caer a defaults razonables cuando no.
|
||||
#[gpui::test]
|
||||
fn natal_options_read_from_configs(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::install_default(cx);
|
||||
let store = Store::in_memory().expect("store");
|
||||
let shell = cx.new(|cx| Shell::new(store, cx));
|
||||
|
||||
shell.update(cx, |s, _cx| {
|
||||
let opts = s.build_natal_options();
|
||||
assert!(opts.show_majors);
|
||||
assert!(!opts.show_minors);
|
||||
assert_eq!(opts.orb_multiplier, 1.0);
|
||||
assert!(!opts.show_dignities);
|
||||
|
||||
s.module_configs.insert(
|
||||
"natal".into(),
|
||||
serde_json::json!({
|
||||
"aspect_majors": true,
|
||||
"aspect_minors": true,
|
||||
"orb_multiplier": 1.75,
|
||||
"show_dignities": true,
|
||||
}),
|
||||
);
|
||||
let opts = s.build_natal_options();
|
||||
assert!(opts.show_minors);
|
||||
assert_eq!(opts.orb_multiplier, 1.75);
|
||||
assert!(opts.show_dignities);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// El flex de los splitters persiste entre instancias de Shell que
|
||||
/// comparten la misma store (in-memory): primera shell escribe via
|
||||
/// `save_split_flex`, segunda shell lee via `load_split_flex` al
|
||||
/// boot.
|
||||
#[test]
|
||||
fn split_flex_round_trip_via_store() {
|
||||
let store = Store::in_memory().expect("store");
|
||||
// No hay nada persistido todavía: defaults.
|
||||
assert_eq!(load_split_flex(&store, "layout.x", 1.0, 4.0), (1.0, 4.0));
|
||||
|
||||
store.set_setting("layout.x", "2.5,3.5").unwrap();
|
||||
assert_eq!(load_split_flex(&store, "layout.x", 1.0, 4.0), (2.5, 3.5));
|
||||
|
||||
// Valor corrupto → defaults.
|
||||
store.set_setting("layout.x", "garbage").unwrap();
|
||||
assert_eq!(load_split_flex(&store, "layout.x", 1.0, 4.0), (1.0, 4.0));
|
||||
|
||||
// Valores ≤ 0 → defaults (los splitters tratan 0 como hidden).
|
||||
store.set_setting("layout.x", "0,5").unwrap();
|
||||
assert_eq!(load_split_flex(&store, "layout.x", 1.0, 4.0), (1.0, 4.0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user