git.lucas.co / cce-data-editor
structured data editor
git clone https://git.lucas.co/cce-data-editor.git

commit318d44617eb91522f6b064b7ab1966fb43d22353
parentf40c64eae4
authorLucas Galante <[email protected]>
date2026-09-11 14:02
Test the span lookup against a fixture, not the live config

test_kdl_span_lookup and test_kdl_roundtrip both read the machine's own
config.kdl, which made the suite's result depend on whose machine it ran
on: the span test failed here for a real bug (fixed in the previous
commit) that no other checkout would have seen, and would equally pass
on a config that happened to dodge it.

Move both onto a FIXTURE holding one of every shape the real configs are
built from — typed scalars, props, props-with-children, nested blocks, a
key list, the joined numeric multi-arg, string lists, an interleaved
comment to shift the byte offsets — and say in its doc comment that new
shapes belong there as shapes rather than as a copy of someone's file.
The fixture reproduces the original failure exactly (same key, same
message) when the old node-indexing logic is put back, so the canary
survives the move.

While the walk is here, assert the span is usable rather than merely
present: non-empty, in bounds, and on character boundaries, since it is
handed straight to byte_to_char_idx.

Co-Authored-By: Claude Opus 5 <[email protected]>

 src/main.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 75 insertions(+), 6 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 5468e6d..9bd5482 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2930,9 +2930,61 @@ fn main() {
 mod tests {
     use super::*;
 
+    /// One of every KDL shape the real configs are built from, so the tests
+    /// below exercise them without reading the machine's own `config.kdl`.
+    /// Reading the live file made the suite's result depend on whose machine
+    /// it ran on — and it did fail, for a year-old span bug nobody had hit
+    /// until a second `bevel_apps` entry appeared there (see
+    /// `span_of_an_indexed_element`). Anything the real config grows that
+    /// this does not cover belongs here, as a shape rather than a copy.
+    // `r##`, not `r#`: the colour literals below contain `"#`.
+    const FIXTURE: &str = r##"
+bell "dialog"
+duration (i64)6
+fonts {
+    monospace "Berkeley Mono"
+    serif "Circe Slab C"
+}
+input {
+    inertial inertial_pointer=(bool)true pointer_friction=(i64)96
+}
+layout {
+    bar_height (i64)24
+    list_bg_color (rgba)"#08080c4c"
+    status_bar light_source="top-left" window="top-left"
+}
+output {
+    eDP-1 scale=(f64)2.0
+}
+style {
+    control corner_radius=(i64)8 {
+        button background=(rgba)"#00000000" font="Berkeley Mono 14" padding=(i64)9
+        relief edge_knobs=(bevel)"0.500,0.500,0.500" width=(f64)9.3
+        tree border_color=(rgb)"#767686" opacity=(f64)0.8 open_search=(keybind)"ctrl+f"
+        window {
+            gutter (mm)2.0
+        }
+    }
+    status {
+        background_color (rgba)"#12141c00"
+    }
+}
+window_manager {
+    // A comment between nodes: spans are byte offsets, so anything that
+    // shifts them belongs in here too.
+    touchpad_view_apps "Houdini FX"
+    center_on_spawn (bool)true
+    corner_shape (f64)4.5
+    light_source_position (radian)2.356194490192345
+    on_app_exit ("menu:focus_previous,overview,none")"focus_previous"
+    bevel_apps "claude-desktop" "com.anthropic.Claude"
+    rounded_apps "claude-desktop" "com.anthropic.Claude" "*chrome*"
+}
+"##;
+
     #[test]
     fn test_kdl_roundtrip() {
-        let content = std::fs::read_to_string(cce_ui::config::get_config_path()).unwrap();
+        let content = FIXTURE.to_string();
         let val = cce_ui::config::parse_kdl_to_json(&content);
         let mut flat = Vec::new();
         flatten_json(&val, "", &mut flat);
@@ -3005,17 +3057,34 @@ key_bindings {
         assert_eq!(span("key_bindings[0].key"), Some("key=\"q\""));
     }
 
+    /// Every key the editor lists must be findable in the text it came from
+    /// — that is what the tree/raw two-way selection rides on — so walk the
+    /// whole fixture rather than spot-checking it.
     #[test]
     fn test_kdl_span_lookup() {
-        let content = std::fs::read_to_string(cce_ui::config::get_config_path()).unwrap();
-        let val = cce_ui::config::parse_kdl_to_json(&content);
+        let content = FIXTURE;
+        let val = cce_ui::config::parse_kdl_to_json(content);
         let mut flat = Vec::new();
         flatten_json(&val, "", &mut flat);
-        
+        assert!(!flat.is_empty(), "the fixture should flatten to something");
+
         for (k, _) in &flat {
             let tokens = parse_path(k);
-            let span = find_kdl_span(&content, &tokens);
-            assert!(span.is_some(), "Should find span for path: {} with tokens: {:?}", k, tokens);
+            let span = find_kdl_span(content, &tokens);
+            let Some((start, end)) = span else {
+                panic!("Should find span for path: {} with tokens: {:?}", k, tokens);
+            };
+            // A span the editor can actually select with: in bounds, not
+            // empty, and on character boundaries (it is handed to
+            // byte_to_char_idx).
+            assert!(start < end, "empty span for {}: {:?}", k, span);
+            assert!(end <= content.len(), "span past the end for {}: {:?}", k, span);
+            assert!(
+                content.is_char_boundary(start) && content.is_char_boundary(end),
+                "span splits a character for {}: {:?}",
+                k,
+                span
+            );
         }
     }
 }