// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package reader import ( "git.stormux.org/storm/fenrir/internal/command" "git.stormux.org/storm/fenrir/internal/presentation" "testing" ) func TestIndentationNavigationModesBlankRowsAndRepeatedLevels(t *testing.T) { for _, mode := range []string{"none", "speech", "beeps", "both"} { t.Run(mode, func(t *testing.T) { r := New() r.Configure_indentation(mode) s := fixture("root", " first", " second", "", " third", "root again") r.Update(s) for _, row := range []int{1, 2, 3, 4, 5} { r.Note_input(true, false, false) s.Y = row output := r.Update(s) wantIndent := mode != "none" && (row == 1 || row == 5) if (len(output) == 2) != wantIndent { t.Fatalf("row %d: %+v", row, output) } if wantIndent { level := 4 if row == 5 { level = 0 } if output[0].Text != presentation.Indentation_message(level, mode).Text || (output[0].Indentation != nil) != (mode == "beeps" || mode == "both") { t.Fatal(output[0]) } if output[0].Indentation != nil && output[0].Indentation.Speech != (mode == "both") { t.Fatal(output[0]) } } if got := r.Update(s); len(got) != 0 { t.Fatal("stationary update repeated feedback", got) } } }) } } func TestIndentationEditsIncludeDedentToZeroWithoutOrdinaryTypingChatter(t *testing.T) { r := New() r.Configure_indentation("speech") s := fixture("value") r.Update(s) for _, trial := range []struct { text string level int }{{" value", 4}, {" value", 2}, {"value", 0}} { s = fixture(trial.text) s.X = trial.level r.Note_input(true, false, false) output := r.Update(s) if len(output) == 0 || output[0].Text != presentation.Indentation_message(trial.level, "speech").Text { t.Fatal(output) } } r.Note_input(true, true, false) s = fixture("Zvalue") s.X = 1 for _, message := range r.Update(s) { if message.Text == "indented 0 " { t.Fatal("typing repeated indentation", message) } } } func TestIndentationDoesNotFollowPassiveOutputDisabledReadingOrOtherTracking(t *testing.T) { for _, trial := range []string{"passive", "automatic off", "cursor off", "table", "highlight", "session", "output", "detached"} { t.Run(trial, func(t *testing.T) { r := New() r.Configure_indentation("both") s := fixture("root", " child") r.Update(s) r.Note_input(true, false, false) s.Y = 1 switch trial { case "passive": r.Note_input(false, false, false) case "automatic off": r.Configure(false, true) case "cursor off": r.Configure(true, false) case "table", "highlight": r.Configure_tracking(trial) case "session": s.Generation++ case "output": s = fixture("root", " new output") s.Y = 1 case "detached": r.Command(command.CurrentLine) r.Note_input(false, false, false) } for _, message := range r.Update(s) { if message.Indentation != nil { t.Fatal(message) } } }) } } func TestReviewIndentationWithAutomaticReadingOffAndApplicationCursorElsewhere(t *testing.T) { r := New() r.Configure_indentation("speech") r.Configure(false, false) s := fixture("root", " child", "", " peer", "root again") r.Update(s) for index, want := range []string{"indented 4 ", "", "", "indented 0 "} { result := r.Command(command.NextLine) got := "" if message := result.Indentation; message != nil { got = message.Text } if got != want { t.Fatal(index, got, want) } r.Update(s) // Passive application state must not reset review's baseline. } result := r.Command(command.CurrentLine) if result.Indentation != nil { t.Fatal("same-line review repeated indentation") } }