package editor import ( "math/rand" "testing" ) // TestWrapIndex_NaiveModel is a property test: a random sequence of // Set / InsertLines / DeleteLines operations on a WrapIndex must keep the // Fenwick structure exactly consistent with a naive []int32 model. func TestWrapIndex_NaiveModel(t *testing.T) { rng := rand.New(rand.NewSource(7)) w := NewWrapIndex(50) model := make([]int32, 50) for i := range model { model[i] = 1 } naivePrefix := func(m []int32, k int) int32 { var s int32 for i := 0; i < k && i < len(m); i++ { s += m[i] } return s } naiveLineForVisual := func(m []int32, v int32) int { if len(m) == 0 { return 0 } if v <= 0 { return 0 } var s int32 for i, c := range m { s += c if s > v { return i } } return len(m) - 1 } ops := 0 for i := 0; i < 3000; i++ { switch rng.Intn(4) { case 0: // Set if len(model) == 0 { continue } li := rng.Intn(len(model)) c := int32(1 + rng.Intn(8)) w.Set(li, c) model[li] = c case 1: // InsertLines pos := rng.Intn(len(model) + 1) n := 1 + rng.Intn(5) w.InsertLines(pos, n) inserted := make([]int32, n) for j := range inserted { inserted[j] = 1 } model = append(model[:pos], append(inserted, model[pos:]...)...) case 2: // DeleteLines if len(model) == 0 { continue } pos := rng.Intn(len(model)) n := 1 + rng.Intn(min(5, len(model)-pos)) w.DeleteLines(pos, n) model = append(model[:pos], model[pos+n:]...) case 3: // SetRange (the shaped-window correction shape) if len(model) == 0 { continue } pos := rng.Intn(len(model)) n := 1 + rng.Intn(min(10, len(model)-pos)) counts := make([]int32, n) for j := range counts { counts[j] = int32(1 + rng.Intn(6)) model[pos+j] = counts[j] } w.SetRange(pos, counts) } ops++ // Full consistency check after every op. if w.Len() != len(model) { t.Fatalf("op %d (%d): Len = %d, model %d", i, ops, w.Len(), len(model)) } for i := 0; i < w.Len(); i++ { if w.Get(i) != model[i] { t.Fatalf("op %d: Get(%d) = %d, model %d", i, i, w.Get(i), model[i]) } } // Prefix sums (V(k)) for a sample of k values plus the ends. for _, k := range []int{0, 1, len(model) / 2, len(model) - 1, len(model), len(model) + 5} { if got, want := w.VisualsBefore(k), naivePrefix(model, k); got != want { t.Fatalf("op %d: VisualsBefore(%d) = %d, want %d", i, k, got, want) } } if got, want := w.TotalVisuals(), naivePrefix(model, len(model)); got != want { t.Fatalf("op %d: TotalVisuals = %d, want %d", i, got, want) } // LineForVisual for every visual line (bounded: total is small here). if total := w.TotalVisuals(); total < 5000 { for v := int32(0); v < total; v++ { if got, want := w.LineForVisual(v), naiveLineForVisual(model, v); got != want { t.Fatalf("op %d: LineForVisual(%d) = %d, want %d", i, v, got, want) } } } } } // TestWrapIndex_AllOnesIsIdentity pins the legacy-mapping property: an // all-ones index (no wrap corrections applied) must map visual lines to // logical lines 1:1, so pre-shaping behavior is exactly the old code path. func TestWrapIndex_AllOnesIsIdentity(t *testing.T) { w := NewWrapIndex(1000) for v := int32(0); v < 1000; v++ { if got := w.LineForVisual(v); got != int(v) { t.Fatalf("LineForVisual(%d) = %d, want %d (all-ones must be identity)", v, got, v) } } for k := 0; k <= 1000; k++ { if got := w.VisualsBefore(k); got != int32(k) { t.Fatalf("VisualsBefore(%d) = %d, want %d", k, got, k) } } if got := w.TotalVisuals(); got != 1000 { t.Fatalf("TotalVisuals = %d, want 1000", got) } } // TestWrapIndex_LineForVisualWrapped checks the mapping against a hand-built // wrap pattern: lines wrap as W = [1,3,1,2,1,...] so V(0)=0, V(1)=1, // V(2)=4, V(3)=5, V(4)=7, V(5)=8. func TestWrapIndex_LineForVisualWrapped(t *testing.T) { w := NewWrapIndex(5) counts := []int32{1, 3, 1, 2, 1} w.SetRange(0, counts) // Visual line v belongs to the smallest k with V(k+1) > v. want := map[int32]int{ 0: 0, 1: 1, 2: 1, 3: 1, // line 0: v0; line 1: v1..v3 4: 2, // line 2: v4 5: 3, 6: 3, // line 3: v5..v6 7: 4, // line 4: v7 8: 4, // clamped: v >= total(8) -> last line 99: 4, } for v, k := range want { if got := w.LineForVisual(v); got != k { t.Errorf("LineForVisual(%d) = %d, want %d", v, got, k) } } if got := w.TotalVisuals(); got != 8 { t.Errorf("TotalVisuals = %d, want 8", got) } }