@@ -617,6 +617,194 @@ describe("App interactions", () => {
617617 }
618618 } ) ;
619619
620+ test ( "Space scrolls down by viewport" , async ( ) => {
621+ // Create a file with many lines so Space has room to scroll
622+ const before =
623+ Array . from (
624+ { length : 50 } ,
625+ ( _ , index ) => `export const line${ String ( index + 1 ) . padStart ( 2 , "0" ) } = ${ index + 1 } ;` ,
626+ ) . join ( "\n" ) + "\n" ;
627+ const after =
628+ Array . from (
629+ { length : 50 } ,
630+ ( _ , index ) => `export const line${ String ( index + 1 ) . padStart ( 2 , "0" ) } = ${ index + 1001 } ;` ,
631+ ) . join ( "\n" ) + "\n" ;
632+
633+ const bootstrap : AppBootstrap = {
634+ input : {
635+ kind : "git" ,
636+ staged : false ,
637+ options : {
638+ mode : "split" ,
639+ } ,
640+ } ,
641+ changeset : {
642+ id : "changeset:space-scroll" ,
643+ sourceLabel : "repo" ,
644+ title : "repo working tree" ,
645+ files : [ createDiffFile ( "space" , "space.ts" , before , after ) ] ,
646+ } ,
647+ initialMode : "split" ,
648+ initialTheme : "midnight" ,
649+ } ;
650+
651+ const setup = await testRender ( < App bootstrap = { bootstrap } /> , {
652+ width : 220 ,
653+ height : 12 ,
654+ } ) ;
655+
656+ try {
657+ await flush ( setup ) ;
658+ setup . captureCharFrame ( ) ; // Capture initial state
659+
660+ // Press Space to scroll down - should not crash and should change view
661+ await act ( async ( ) => {
662+ await setup . mockInput . pressKey ( " " ) ;
663+ } ) ;
664+ await flush ( setup ) ;
665+
666+ // App should still be rendering without errors
667+ const frame = setup . captureCharFrame ( ) ;
668+ expect ( frame ) . toContain ( "export const line" ) ;
669+ } finally {
670+ await act ( async ( ) => {
671+ setup . renderer . destroy ( ) ;
672+ } ) ;
673+ }
674+ } ) ;
675+
676+ test ( "PageUp scrolls up by viewport" , async ( ) => {
677+ const before =
678+ Array . from (
679+ { length : 50 } ,
680+ ( _ , index ) => `export const line${ String ( index + 1 ) . padStart ( 2 , "0" ) } = ${ index + 1 } ;` ,
681+ ) . join ( "\n" ) + "\n" ;
682+ const after =
683+ Array . from (
684+ { length : 50 } ,
685+ ( _ , index ) => `export const line${ String ( index + 1 ) . padStart ( 2 , "0" ) } = ${ index + 1001 } ;` ,
686+ ) . join ( "\n" ) + "\n" ;
687+
688+ const bootstrap : AppBootstrap = {
689+ input : {
690+ kind : "git" ,
691+ staged : false ,
692+ options : {
693+ mode : "split" ,
694+ } ,
695+ } ,
696+ changeset : {
697+ id : "changeset:pageup-scroll" ,
698+ sourceLabel : "repo" ,
699+ title : "repo working tree" ,
700+ files : [ createDiffFile ( "pageup" , "pageup.ts" , before , after ) ] ,
701+ } ,
702+ initialMode : "split" ,
703+ initialTheme : "midnight" ,
704+ } ;
705+
706+ const setup = await testRender ( < App bootstrap = { bootstrap } /> , {
707+ width : 220 ,
708+ height : 12 ,
709+ } ) ;
710+
711+ try {
712+ await flush ( setup ) ;
713+
714+ // First scroll down to have room to scroll back up
715+ await act ( async ( ) => {
716+ await setup . mockInput . pressKey ( " " ) ;
717+ } ) ;
718+ await flush ( setup ) ;
719+ setup . captureCharFrame ( ) ; // Capture after Space
720+
721+ // Now scroll back up with PageUp - should not crash
722+ await act ( async ( ) => {
723+ await setup . mockInput . pressKey ( "pageup" ) ;
724+ } ) ;
725+ await flush ( setup ) ;
726+
727+ // App should still be rendering
728+ const frame = setup . captureCharFrame ( ) ;
729+ expect ( frame ) . toContain ( "export const line" ) ;
730+ } finally {
731+ await act ( async ( ) => {
732+ setup . renderer . destroy ( ) ;
733+ } ) ;
734+ }
735+ } ) ;
736+
737+ test ( "new shortcuts d, u, and f work without errors" , async ( ) => {
738+ const before =
739+ Array . from (
740+ { length : 50 } ,
741+ ( _ , index ) => `export const line${ String ( index + 1 ) . padStart ( 2 , "0" ) } = ${ index + 1 } ;` ,
742+ ) . join ( "\n" ) + "\n" ;
743+ const after =
744+ Array . from (
745+ { length : 50 } ,
746+ ( _ , index ) => `export const line${ String ( index + 1 ) . padStart ( 2 , "0" ) } = ${ index + 1001 } ;` ,
747+ ) . join ( "\n" ) + "\n" ;
748+
749+ const bootstrap : AppBootstrap = {
750+ input : {
751+ kind : "git" ,
752+ staged : false ,
753+ options : {
754+ mode : "split" ,
755+ } ,
756+ } ,
757+ changeset : {
758+ id : "changeset:half-scroll" ,
759+ sourceLabel : "repo" ,
760+ title : "repo working tree" ,
761+ files : [ createDiffFile ( "half" , "half.ts" , before , after ) ] ,
762+ } ,
763+ initialMode : "split" ,
764+ initialTheme : "midnight" ,
765+ } ;
766+
767+ const setup = await testRender ( < App bootstrap = { bootstrap } /> , {
768+ width : 220 ,
769+ height : 12 ,
770+ } ) ;
771+
772+ try {
773+ await flush ( setup ) ;
774+ const initialFrame = setup . captureCharFrame ( ) ;
775+
776+ // Press 'd' (half page down) - should not crash
777+ await act ( async ( ) => {
778+ await setup . mockInput . pressKey ( "d" ) ;
779+ } ) ;
780+ await flush ( setup ) ;
781+ let frame = setup . captureCharFrame ( ) ;
782+ expect ( frame ) . toContain ( "export const line" ) ;
783+
784+ // Press 'u' (half page up) - should not crash
785+ await act ( async ( ) => {
786+ await setup . mockInput . pressKey ( "u" ) ;
787+ } ) ;
788+ await flush ( setup ) ;
789+ frame = setup . captureCharFrame ( ) ;
790+ expect ( frame ) . toContain ( "export const line" ) ;
791+
792+ // Press 'f' (page down, less-style) - should not crash
793+ await act ( async ( ) => {
794+ await setup . mockInput . pressKey ( "f" ) ;
795+ } ) ;
796+ await flush ( setup ) ;
797+ frame = setup . captureCharFrame ( ) ;
798+ expect ( frame ) . toContain ( "export const line" ) ;
799+ // Content should have changed after scrolling
800+ expect ( frame ) . not . toEqual ( initialFrame ) ;
801+ } finally {
802+ await act ( async ( ) => {
803+ setup . renderer . destroy ( ) ;
804+ } ) ;
805+ }
806+ } ) ;
807+
620808 test ( "filter focus accepts typed input and narrows the visible file set" , async ( ) => {
621809 const setup = await testRender ( < App bootstrap = { createBootstrap ( ) } /> , {
622810 width : 240 ,
0 commit comments