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:
@@ -26,6 +26,10 @@ gpui = { workspace = true }
|
|||||||
directories = { workspace = true }
|
directories = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# TestAppContext + #[gpui::test] para tests de integración del shell.
|
||||||
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tahuantinsuyu"
|
name = "tahuantinsuyu"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|||||||
@@ -1016,3 +1016,178 @@ impl Render for Shell {
|
|||||||
.child(body)
|
.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