@@ -567,6 +567,86 @@ func TestHandleSearchResultsAndObservationDetailRemainingBranches(t *testing.T)
567567 }
568568}
569569
570+ func TestSearchEscapeFlowNoLoop (t * testing.T ) {
571+ // Verifies the full escape chain never loops back:
572+ // Dashboard → Search → Results → ObsDetail → Esc → Results → Esc → Search → Esc → Dashboard
573+
574+ m := New (nil , "" )
575+ m .Height = 20
576+ m .SearchResults = []store.SearchResult {{Observation : store.Observation {ID : 42 }}}
577+
578+ // Step 1: from SearchResults, enter ObservationDetail — PrevScreen = ScreenSearchResults
579+ m .Screen = ScreenSearchResults
580+ m .Cursor = 0
581+ updatedModel , _ := m .handleSearchResultsKeys ("enter" )
582+ m = updatedModel .(Model )
583+ if m .PrevScreen != ScreenSearchResults {
584+ t .Fatalf ("after enter, PrevScreen should be ScreenSearchResults, got %v" , m .PrevScreen )
585+ }
586+
587+ // Step 2: from ObservationDetail, Esc → back to SearchResults (via PrevScreen)
588+ m .Screen = ScreenObservationDetail
589+ m .SelectedObservation = & store.Observation {ID : 42 }
590+ updatedModel , _ = m .handleObservationDetailKeys ("esc" )
591+ m = updatedModel .(Model )
592+ if m .Screen != ScreenSearchResults {
593+ t .Fatalf ("esc from ObservationDetail should go to ScreenSearchResults, got %v" , m .Screen )
594+ }
595+
596+ // Step 3: from SearchResults, Esc → back to ScreenSearch, PrevScreen reset to Dashboard
597+ m .Screen = ScreenSearchResults
598+ updatedModel , _ = m .handleSearchResultsKeys ("esc" )
599+ m = updatedModel .(Model )
600+ if m .Screen != ScreenSearch {
601+ t .Fatalf ("esc from SearchResults should go to ScreenSearch, got %v" , m .Screen )
602+ }
603+ if m .PrevScreen != ScreenDashboard {
604+ t .Fatalf ("esc from SearchResults should reset PrevScreen to ScreenDashboard, got %v" , m .PrevScreen )
605+ }
606+
607+ // Step 4: from Search (no input focused), Esc → always Dashboard, never loops
608+ m .Screen = ScreenSearch
609+ updatedModel , _ = m .handleSearchKeys ("esc" )
610+ m = updatedModel .(Model )
611+ if m .Screen != ScreenDashboard {
612+ t .Fatalf ("esc from Search should always go to ScreenDashboard, got %v" , m .Screen )
613+ }
614+
615+ // Step 5: from Search input focused, Esc → always Dashboard, never loops
616+ m .Screen = ScreenSearch
617+ m .PrevScreen = ScreenSearchResults // simulate stale PrevScreen — must NOT be used
618+ m .SearchInput .Focus ()
619+ updatedModel , _ = m .handleSearchInputKeys (tea.KeyMsg {Type : tea .KeyEscape })
620+ m = updatedModel .(Model )
621+ if m .Screen != ScreenDashboard {
622+ t .Fatalf ("esc from SearchInput should always go to ScreenDashboard regardless of PrevScreen, got %v" , m .Screen )
623+ }
624+ }
625+
626+ func TestSearchInputClearedOnEnterFromDashboard (t * testing.T ) {
627+ // Verifies the search input is cleared each time search is opened from dashboard
628+ m := New (nil , "" )
629+ m .Screen = ScreenDashboard
630+ m .SearchInput .SetValue ("old query" )
631+
632+ // Open via keyboard shortcut "s"
633+ updatedModel , _ := m .handleDashboardKeys ("s" )
634+ m = updatedModel .(Model )
635+ if m .SearchInput .Value () != "" {
636+ t .Fatalf ("search input should be cleared when opening search, got %q" , m .SearchInput .Value ())
637+ }
638+
639+ // Open via dashboard selection (menu item 0)
640+ m .Screen = ScreenDashboard
641+ m .SearchInput .SetValue ("another stale query" )
642+ m .Cursor = 0
643+ updatedModel , _ = m .handleDashboardSelection ()
644+ m = updatedModel .(Model )
645+ if m .SearchInput .Value () != "" {
646+ t .Fatalf ("search input should be cleared on dashboard selection, got %q" , m .SearchInput .Value ())
647+ }
648+ }
649+
570650func TestHandleSessionsAndSetupRemainingBranches (t * testing.T ) {
571651 fx := newTestFixture (t )
572652 m := New (fx .store , "" )
0 commit comments