@@ -843,3 +843,251 @@ func TestTerminalRegisterOscHandler(t *testing.T) {
843843 }
844844}
845845
846+ // fillScrollback writes enough lines to the terminal to create scrollback content.
847+ func fillScrollback (term * Terminal , lineCount int ) {
848+ for i := range lineCount {
849+ term .WriteString (fmt .Sprintf ("line %d\r \n " , i ))
850+ }
851+ }
852+
853+ func TestTerminalScrollLines (t * testing.T ) {
854+ t .Parallel ()
855+
856+ type Expectation struct {
857+ YDisp int
858+ YBase int
859+ }
860+
861+ term := newTestTerminal (80 , 5 )
862+ fillScrollback (term , 20 )
863+
864+ yBase := term .Buffer ().YBase
865+
866+ // Scroll up 3 lines
867+ term .ScrollLines (- 3 )
868+
869+ got := Expectation {
870+ YDisp : term .Buffer ().YDisp ,
871+ YBase : term .Buffer ().YBase ,
872+ }
873+ expected := Expectation {
874+ YDisp : yBase - 3 ,
875+ YBase : yBase ,
876+ }
877+ if diff := cmp .Diff (expected , got ); diff != "" {
878+ t .Errorf ("(-want +got):\n %s" , diff )
879+ }
880+ }
881+
882+ func TestTerminalScrollLinesDown (t * testing.T ) {
883+ t .Parallel ()
884+
885+ term := newTestTerminal (80 , 5 )
886+ fillScrollback (term , 20 )
887+
888+ // Scroll up then back down
889+ term .ScrollLines (- 5 )
890+ term .ScrollLines (5 )
891+
892+ if term .Buffer ().YDisp != term .Buffer ().YBase {
893+ t .Errorf ("YDisp = %d, want %d (YBase)" , term .Buffer ().YDisp , term .Buffer ().YBase )
894+ }
895+ }
896+
897+ func TestTerminalScrollPages (t * testing.T ) {
898+ t .Parallel ()
899+
900+ term := newTestTerminal (80 , 5 )
901+ fillScrollback (term , 30 )
902+
903+ yBase := term .Buffer ().YBase
904+
905+ // Scroll up 2 pages (2 * 5 rows = 10 lines)
906+ term .ScrollPages (- 2 )
907+
908+ expected := yBase - 10
909+ if term .Buffer ().YDisp != expected {
910+ t .Errorf ("YDisp = %d, want %d" , term .Buffer ().YDisp , expected )
911+ }
912+ }
913+
914+ func TestTerminalScrollToTop (t * testing.T ) {
915+ t .Parallel ()
916+
917+ term := newTestTerminal (80 , 5 )
918+ fillScrollback (term , 20 )
919+
920+ term .ScrollToTop ()
921+
922+ if term .Buffer ().YDisp != 0 {
923+ t .Errorf ("YDisp = %d, want 0" , term .Buffer ().YDisp )
924+ }
925+ }
926+
927+ func TestTerminalScrollToBottom (t * testing.T ) {
928+ t .Parallel ()
929+
930+ term := newTestTerminal (80 , 5 )
931+ fillScrollback (term , 20 )
932+
933+ // Scroll to top first, then back to bottom
934+ term .ScrollToTop ()
935+ term .ScrollToBottom ()
936+
937+ if term .Buffer ().YDisp != term .Buffer ().YBase {
938+ t .Errorf ("YDisp = %d, want %d (YBase)" , term .Buffer ().YDisp , term .Buffer ().YBase )
939+ }
940+ }
941+
942+ func TestTerminalScrollToLine (t * testing.T ) {
943+ t .Parallel ()
944+
945+ term := newTestTerminal (80 , 5 )
946+ fillScrollback (term , 20 )
947+
948+ term .ScrollToLine (5 )
949+
950+ if term .Buffer ().YDisp != 5 {
951+ t .Errorf ("YDisp = %d, want 5" , term .Buffer ().YDisp )
952+ }
953+ }
954+
955+ func TestTerminalScrollToLineClamps (t * testing.T ) {
956+ t .Parallel ()
957+
958+ term := newTestTerminal (80 , 5 )
959+ fillScrollback (term , 20 )
960+
961+ // Scroll to a line beyond YBase — should clamp to YBase
962+ term .ScrollToLine (9999 )
963+
964+ if term .Buffer ().YDisp != term .Buffer ().YBase {
965+ t .Errorf ("YDisp = %d, want %d (YBase)" , term .Buffer ().YDisp , term .Buffer ().YBase )
966+ }
967+ }
968+
969+ func TestTerminalScrollNoScrollback (t * testing.T ) {
970+ t .Parallel ()
971+
972+ term := newTestTerminal (80 , 5 )
973+ // No scrollback content — scrolling should be a no-op
974+ term .ScrollLines (- 5 )
975+
976+ if term .Buffer ().YDisp != 0 {
977+ t .Errorf ("YDisp = %d, want 0" , term .Buffer ().YDisp )
978+ }
979+ }
980+
981+ func TestTerminalClear (t * testing.T ) {
982+ t .Parallel ()
983+
984+ type Expectation struct {
985+ YBase int
986+ YDisp int
987+ IsUserScrolling bool
988+ }
989+
990+ term := newTestTerminal (80 , 5 )
991+ fillScrollback (term , 20 )
992+
993+ // Scroll up to simulate user scrolling
994+ term .ScrollToTop ()
995+
996+ term .Clear ()
997+
998+ got := Expectation {
999+ YBase : term .Buffer ().YBase ,
1000+ YDisp : term .Buffer ().YDisp ,
1001+ IsUserScrolling : term .bufferService .IsUserScrolling ,
1002+ }
1003+ expected := Expectation {
1004+ YBase : 0 ,
1005+ YDisp : 0 ,
1006+ IsUserScrolling : false ,
1007+ }
1008+ if diff := cmp .Diff (expected , got ); diff != "" {
1009+ t .Errorf ("(-want +got):\n %s" , diff )
1010+ }
1011+ }
1012+
1013+ func TestTerminalClearEmptyTerminal (t * testing.T ) {
1014+ t .Parallel ()
1015+
1016+ term := newTestTerminal (80 , 5 )
1017+ // Clear on empty terminal should be a no-op (no panic)
1018+ term .Clear ()
1019+
1020+ if term .Buffer ().YBase != 0 {
1021+ t .Errorf ("YBase = %d, want 0" , term .Buffer ().YBase )
1022+ }
1023+ }
1024+
1025+ func TestTerminalClearFiresScrollEvent (t * testing.T ) {
1026+ t .Parallel ()
1027+
1028+ term := newTestTerminal (80 , 5 )
1029+ fillScrollback (term , 20 )
1030+
1031+ scrollFired := false
1032+ term .OnScroll (func (int ) { scrollFired = true })
1033+
1034+ term .Clear ()
1035+
1036+ if ! scrollFired {
1037+ t .Error ("expected OnScroll to fire after Clear()" )
1038+ }
1039+ }
1040+
1041+ func TestTerminalAddMarker (t * testing.T ) {
1042+ t .Parallel ()
1043+
1044+ term := newTestTerminal (80 , 24 )
1045+ term .WriteString ("hello\r \n world\r \n " )
1046+
1047+ // Cursor is at row 2 (0-based), add marker at cursor position
1048+ marker := term .AddMarker (0 )
1049+ if marker == nil {
1050+ t .Fatal ("AddMarker returned nil" )
1051+ }
1052+
1053+ expectedLine := term .Buffer ().YBase + term .CursorY ()
1054+ if marker .Line != expectedLine {
1055+ t .Errorf ("marker.Line = %d, want %d" , marker .Line , expectedLine )
1056+ }
1057+ }
1058+
1059+ func TestTerminalAddMarkerWithOffset (t * testing.T ) {
1060+ t .Parallel ()
1061+
1062+ term := newTestTerminal (80 , 24 )
1063+ term .WriteString ("line1\r \n line2\r \n line3\r \n " )
1064+
1065+ // Add marker 1 line above cursor
1066+ marker := term .AddMarker (- 1 )
1067+ if marker == nil {
1068+ t .Fatal ("AddMarker returned nil" )
1069+ }
1070+
1071+ expectedLine := term .Buffer ().YBase + term .CursorY () - 1
1072+ if marker .Line != expectedLine {
1073+ t .Errorf ("marker.Line = %d, want %d" , marker .Line , expectedLine )
1074+ }
1075+ }
1076+
1077+ func TestTerminalScrollLinesFiresEvent (t * testing.T ) {
1078+ t .Parallel ()
1079+
1080+ term := newTestTerminal (80 , 5 )
1081+ fillScrollback (term , 20 )
1082+
1083+ scrollEvents := 0
1084+ term .OnScroll (func (int ) { scrollEvents ++ })
1085+
1086+ term .ScrollLines (- 3 )
1087+
1088+ if scrollEvents != 1 {
1089+ t .Errorf ("scroll events = %d, want 1" , scrollEvents )
1090+ }
1091+ }
1092+
1093+
0 commit comments