@@ -297,6 +297,10 @@ function firstVisibleAddedLine(frame: string) {
297297 return frame . match ( / l i n e \d { 2 } = 1 \d { 2 } / ) ?. [ 0 ] ?? null ;
298298}
299299
300+ function firstVisibleAddedLineNumber ( frame : string ) {
301+ return frame . match ( / ▌ \s * ( \d + ) \s + \+ / ) ?. [ 1 ] ?? null ;
302+ }
303+
300304describe ( "App interactions" , ( ) => {
301305 test ( "keyboard shortcuts toggle notes, line numbers, and hunk metadata" , async ( ) => {
302306 const setup = await testRender ( < AppHost bootstrap = { createSingleFileBootstrap ( ) } /> , {
@@ -450,6 +454,235 @@ describe("App interactions", () => {
450454 }
451455 } ) ;
452456
457+ test ( "left and right arrows can reveal offscreen code columns in nowrap mode" , async ( ) => {
458+ const setup = await testRender ( < AppHost bootstrap = { createWrapBootstrap ( ) } /> , {
459+ width : 92 ,
460+ height : 20 ,
461+ } ) ;
462+
463+ try {
464+ await flush ( setup ) ;
465+
466+ let frame = setup . captureCharFrame ( ) ;
467+ expect ( frame ) . toContain ( "this is a very" ) ;
468+ expect ( frame ) . not . toContain ( "interaction coverage" ) ;
469+
470+ for ( let index = 0 ; index < 64 ; index += 1 ) {
471+ await act ( async ( ) => {
472+ await setup . mockInput . pressArrow ( "right" ) ;
473+ } ) ;
474+ await flush ( setup ) ;
475+ frame = setup . captureCharFrame ( ) ;
476+ if ( frame . includes ( "interaction coverage" ) ) {
477+ break ;
478+ }
479+ }
480+
481+ expect ( frame ) . toContain ( "interaction coverage" ) ;
482+ expect ( frame ) . not . toContain ( "this is a very" ) ;
483+
484+ for ( let index = 0 ; index < 64 ; index += 1 ) {
485+ await act ( async ( ) => {
486+ await setup . mockInput . pressArrow ( "left" ) ;
487+ } ) ;
488+ await flush ( setup ) ;
489+ frame = setup . captureCharFrame ( ) ;
490+ if ( frame . includes ( "this is a very" ) ) {
491+ break ;
492+ }
493+ }
494+
495+ expect ( frame ) . toContain ( "this is a very" ) ;
496+ } finally {
497+ await act ( async ( ) => {
498+ setup . renderer . destroy ( ) ;
499+ } ) ;
500+ }
501+ } ) ;
502+
503+ test ( "shift plus left and right arrows scroll code horizontally faster" , async ( ) => {
504+ const setup = await testRender ( < AppHost bootstrap = { createWrapBootstrap ( ) } /> , {
505+ width : 92 ,
506+ height : 20 ,
507+ } ) ;
508+
509+ try {
510+ await flush ( setup ) ;
511+
512+ let frame = setup . captureCharFrame ( ) ;
513+ expect ( frame ) . toContain ( "this is a very" ) ;
514+ expect ( frame ) . not . toContain ( "interaction coverage" ) ;
515+
516+ for ( let index = 0 ; index < 8 ; index += 1 ) {
517+ await act ( async ( ) => {
518+ await setup . mockInput . pressArrow ( "right" , { shift : true } ) ;
519+ } ) ;
520+ await flush ( setup ) ;
521+ frame = setup . captureCharFrame ( ) ;
522+ if ( frame . includes ( "interaction coverage" ) ) {
523+ break ;
524+ }
525+ }
526+
527+ expect ( frame ) . toContain ( "interaction coverage" ) ;
528+ expect ( frame ) . not . toContain ( "this is a very" ) ;
529+
530+ for ( let index = 0 ; index < 8 ; index += 1 ) {
531+ await act ( async ( ) => {
532+ await setup . mockInput . pressArrow ( "left" , { shift : true } ) ;
533+ } ) ;
534+ await flush ( setup ) ;
535+ frame = setup . captureCharFrame ( ) ;
536+ if ( frame . includes ( "this is a very" ) ) {
537+ break ;
538+ }
539+ }
540+
541+ expect ( frame ) . toContain ( "this is a very" ) ;
542+ expect ( frame ) . not . toContain ( "interaction coverage" ) ;
543+ } finally {
544+ await act ( async ( ) => {
545+ setup . renderer . destroy ( ) ;
546+ } ) ;
547+ }
548+ } ) ;
549+
550+ test ( "shift plus mouse wheel scrolls code horizontally" , async ( ) => {
551+ const setup = await testRender ( < AppHost bootstrap = { createWrapBootstrap ( ) } /> , {
552+ width : 92 ,
553+ height : 20 ,
554+ } ) ;
555+
556+ try {
557+ await flush ( setup ) ;
558+
559+ let frame = setup . captureCharFrame ( ) ;
560+ expect ( frame ) . toContain ( "this is a very" ) ;
561+ expect ( frame ) . not . toContain ( "interaction coverage" ) ;
562+
563+ for ( let index = 0 ; index < 8 ; index += 1 ) {
564+ await act ( async ( ) => {
565+ await setup . mockMouse . scroll ( 60 , 10 , "down" , { modifiers : { shift : true } } ) ;
566+ } ) ;
567+ await flush ( setup ) ;
568+ frame = setup . captureCharFrame ( ) ;
569+ if ( frame . includes ( "interaction coverage" ) ) {
570+ break ;
571+ }
572+ }
573+
574+ expect ( frame ) . toContain ( "interaction coverage" ) ;
575+ expect ( frame ) . not . toContain ( "this is a very" ) ;
576+
577+ for ( let index = 0 ; index < 8 ; index += 1 ) {
578+ await act ( async ( ) => {
579+ await setup . mockMouse . scroll ( 60 , 10 , "up" , { modifiers : { shift : true } } ) ;
580+ } ) ;
581+ await flush ( setup ) ;
582+ frame = setup . captureCharFrame ( ) ;
583+ if ( frame . includes ( "this is a very" ) ) {
584+ break ;
585+ }
586+ }
587+
588+ expect ( frame ) . toContain ( "this is a very" ) ;
589+ expect ( frame ) . not . toContain ( "interaction coverage" ) ;
590+ } finally {
591+ await act ( async ( ) => {
592+ setup . renderer . destroy ( ) ;
593+ } ) ;
594+ }
595+ } ) ;
596+
597+ test ( "shift plus mouse wheel does not move the vertical review position" , async ( ) => {
598+ const setup = await testRender ( < AppHost bootstrap = { createWrapScrollBootstrap ( ) } /> , {
599+ width : 92 ,
600+ height : 20 ,
601+ } ) ;
602+
603+ try {
604+ await flush ( setup ) ;
605+
606+ let frame = setup . captureCharFrame ( ) ;
607+ const initialTopLine = firstVisibleAddedLineNumber ( frame ) ;
608+ expect ( initialTopLine ) . toBeTruthy ( ) ;
609+ expect ( frame ) . not . toContain ( "viewport anchoring" ) ;
610+
611+ for ( let index = 0 ; index < 8 ; index += 1 ) {
612+ await act ( async ( ) => {
613+ await setup . mockMouse . scroll ( 60 , 10 , "down" , { modifiers : { shift : true } } ) ;
614+ } ) ;
615+ await flush ( setup ) ;
616+ frame = setup . captureCharFrame ( ) ;
617+ if ( frame . includes ( "viewport anchoring" ) ) {
618+ break ;
619+ }
620+ }
621+
622+ expect ( frame ) . toContain ( "viewport anchoring" ) ;
623+ expect ( firstVisibleAddedLineNumber ( frame ) ) . toBe ( initialTopLine ) ;
624+ } finally {
625+ await act ( async ( ) => {
626+ setup . renderer . destroy ( ) ;
627+ } ) ;
628+ }
629+ } ) ;
630+
631+ test ( "wrap toggles reset the horizontal code offset" , async ( ) => {
632+ const setup = await testRender ( < AppHost bootstrap = { createWrapBootstrap ( ) } /> , {
633+ width : 92 ,
634+ height : 20 ,
635+ } ) ;
636+
637+ try {
638+ await flush ( setup ) ;
639+
640+ let frame = setup . captureCharFrame ( ) ;
641+ expect ( frame ) . toContain ( "this is a very" ) ;
642+
643+ for ( let index = 0 ; index < 8 ; index += 1 ) {
644+ await act ( async ( ) => {
645+ await setup . mockInput . pressArrow ( "right" , { shift : true } ) ;
646+ } ) ;
647+ await flush ( setup ) ;
648+ frame = setup . captureCharFrame ( ) ;
649+ if ( frame . includes ( "interaction coverage" ) ) {
650+ break ;
651+ }
652+ }
653+
654+ expect ( frame ) . toContain ( "interaction coverage" ) ;
655+ expect ( frame ) . not . toContain ( "this is a very" ) ;
656+
657+ await act ( async ( ) => {
658+ await setup . mockInput . typeText ( "w" ) ;
659+ } ) ;
660+ await settleWrapToggle ( setup ) ;
661+
662+ frame = await waitForFrame ( setup , ( nextFrame ) => nextFrame . includes ( "overage';" ) ) ;
663+ expect ( frame ) . toContain ( "this is a very" ) ;
664+ expect ( frame ) . toContain ( "long wrapped line" ) ;
665+ expect ( frame ) . toContain ( "overage';" ) ;
666+
667+ await act ( async ( ) => {
668+ await setup . mockInput . typeText ( "w" ) ;
669+ } ) ;
670+ await settleWrapToggle ( setup ) ;
671+
672+ frame = await waitForFrame (
673+ setup ,
674+ ( nextFrame ) =>
675+ nextFrame . includes ( "this is a very" ) && ! nextFrame . includes ( "interaction coverage" ) ,
676+ ) ;
677+ expect ( frame ) . toContain ( "this is a very" ) ;
678+ expect ( frame ) . not . toContain ( "interaction coverage" ) ;
679+ } finally {
680+ await act ( async ( ) => {
681+ setup . renderer . destroy ( ) ;
682+ } ) ;
683+ }
684+ } ) ;
685+
453686 test ( "bootstrap preferences initialize the visible view state" , async ( ) => {
454687 const setup = await testRender (
455688 < AppHost
0 commit comments