tawasuyu

tawasuyu/brahman

Rust 212TOML 64JSON 13HTML 12666 Definitions

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>

d2b6b8b12e2739473e9454b9107161c5907fba93 · by sergio · (5 months ago) · parent e044d4751 · browse the tree at this commit

Semantic delta · 7

2 files · +179 −0

diff --git a/crates/apps/tahuantinsuyu/Cargo.toml b/crates/apps/tahuantinsuyu/Cargo.toml
index 03a85fc..d5c6260 100644
--- a/crates/apps/tahuantinsuyu/Cargo.toml
+++ b/crates/apps/tahuantinsuyu/Cargo.toml
@@ -26,6 +26,10 @@ gpui = { workspace = true }
 directories = { 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]]
 name = "tahuantinsuyu"
 path = "src/main.rs"
diff --git a/crates/apps/tahuantinsuyu/src/shell.rs b/crates/apps/tahuantinsuyu/src/shell.rs
index f135af4..34d5f5e 100644
--- a/crates/apps/tahuantinsuyu/src/shell.rs
+++ b/crates/apps/tahuantinsuyu/src/shell.rs
@@ -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));
+    }
+}