@@ -7558,4 +7558,306 @@ describe("PullRequestDetail", () => {
75587558 expect ( output ) . toContain ( "baz" ) ;
75597559 } ) ;
75607560 } ) ;
7561+
7562+ describe ( "split view" , ( ) => {
7563+ it ( "toggles to split view with s key" , async ( ) => {
7564+ const { stdin, lastFrame } = render (
7565+ < PullRequestDetail
7566+ pullRequest = { pullRequest as any }
7567+ differences = { differences as any }
7568+ commentThreads = { [ ] }
7569+ diffTexts = { diffTexts }
7570+ onBack = { vi . fn ( ) }
7571+ onHelp = { vi . fn ( ) }
7572+ onShowActivity = { vi . fn ( ) }
7573+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7574+ inlineComment = { defaultInlineCommentProps }
7575+ reply = { defaultReplyProps }
7576+ approval = { defaultApprovalProps }
7577+ merge = { defaultMergeProps }
7578+ close = { defaultCloseProps }
7579+ commitView = { defaultCommitProps }
7580+ editComment = { defaultEditCommentProps }
7581+ deleteComment = { defaultDeleteCommentProps }
7582+ reaction = { defaultReactionProps }
7583+ terminalWidth = { 120 }
7584+ /> ,
7585+ ) ;
7586+ stdin . write ( "s" ) ;
7587+ await vi . waitFor ( ( ) => {
7588+ expect ( lastFrame ( ) ) . toContain ( "s unified" ) ;
7589+ } ) ;
7590+ const output = lastFrame ( ) ?? "" ;
7591+ expect ( output ) . toContain ( "│" ) ;
7592+ } ) ;
7593+
7594+ it ( "toggles back to unified view with s key" , async ( ) => {
7595+ const { stdin, lastFrame } = render (
7596+ < PullRequestDetail
7597+ pullRequest = { pullRequest as any }
7598+ differences = { differences as any }
7599+ commentThreads = { [ ] }
7600+ diffTexts = { diffTexts }
7601+ onBack = { vi . fn ( ) }
7602+ onHelp = { vi . fn ( ) }
7603+ onShowActivity = { vi . fn ( ) }
7604+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7605+ inlineComment = { defaultInlineCommentProps }
7606+ reply = { defaultReplyProps }
7607+ approval = { defaultApprovalProps }
7608+ merge = { defaultMergeProps }
7609+ close = { defaultCloseProps }
7610+ commitView = { defaultCommitProps }
7611+ editComment = { defaultEditCommentProps }
7612+ deleteComment = { defaultDeleteCommentProps }
7613+ reaction = { defaultReactionProps }
7614+ terminalWidth = { 120 }
7615+ /> ,
7616+ ) ;
7617+ stdin . write ( "s" ) ;
7618+ await vi . waitFor ( ( ) => {
7619+ expect ( lastFrame ( ) ) . toContain ( "s unified" ) ;
7620+ } ) ;
7621+ stdin . write ( "s" ) ;
7622+ await vi . waitFor ( ( ) => {
7623+ expect ( lastFrame ( ) ) . toContain ( "s split" ) ;
7624+ } ) ;
7625+ } ) ;
7626+
7627+ it ( "falls back to unified when terminal is too narrow" , ( ) => {
7628+ const { stdin, lastFrame } = render (
7629+ < PullRequestDetail
7630+ pullRequest = { pullRequest as any }
7631+ differences = { differences as any }
7632+ commentThreads = { [ ] }
7633+ diffTexts = { diffTexts }
7634+ onBack = { vi . fn ( ) }
7635+ onHelp = { vi . fn ( ) }
7636+ onShowActivity = { vi . fn ( ) }
7637+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7638+ inlineComment = { defaultInlineCommentProps }
7639+ reply = { defaultReplyProps }
7640+ approval = { defaultApprovalProps }
7641+ merge = { defaultMergeProps }
7642+ close = { defaultCloseProps }
7643+ commitView = { defaultCommitProps }
7644+ editComment = { defaultEditCommentProps }
7645+ deleteComment = { defaultDeleteCommentProps }
7646+ reaction = { defaultReactionProps }
7647+ terminalWidth = { 80 }
7648+ /> ,
7649+ ) ;
7650+ stdin . write ( "s" ) ;
7651+ const output = lastFrame ( ) ?? "" ;
7652+ // 80列ではsplit view区切り線が表示されない
7653+ expect ( output ) . not . toContain ( "s split" ) ;
7654+ expect ( output ) . not . toContain ( "s unified" ) ;
7655+ } ) ;
7656+
7657+ it ( "navigates with j/k in split mode" , async ( ) => {
7658+ const { stdin, lastFrame } = render (
7659+ < PullRequestDetail
7660+ pullRequest = { pullRequest as any }
7661+ differences = { differences as any }
7662+ commentThreads = { [ ] }
7663+ diffTexts = { diffTexts }
7664+ onBack = { vi . fn ( ) }
7665+ onHelp = { vi . fn ( ) }
7666+ onShowActivity = { vi . fn ( ) }
7667+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7668+ inlineComment = { defaultInlineCommentProps }
7669+ reply = { defaultReplyProps }
7670+ approval = { defaultApprovalProps }
7671+ merge = { defaultMergeProps }
7672+ close = { defaultCloseProps }
7673+ commitView = { defaultCommitProps }
7674+ editComment = { defaultEditCommentProps }
7675+ deleteComment = { defaultDeleteCommentProps }
7676+ reaction = { defaultReactionProps }
7677+ terminalWidth = { 120 }
7678+ /> ,
7679+ ) ;
7680+ stdin . write ( "s" ) ;
7681+ await vi . waitFor ( ( ) => {
7682+ expect ( lastFrame ( ) ) . toContain ( "s unified" ) ;
7683+ } ) ;
7684+ stdin . write ( "j" ) ;
7685+ stdin . write ( "j" ) ;
7686+ const output = lastFrame ( ) ?? "" ;
7687+ expect ( output ) . toContain ( "> " ) ;
7688+ } ) ;
7689+
7690+ it ( "navigates with n/N in split mode" , async ( ) => {
7691+ const multiFileDiffTexts = new Map ( [
7692+ [ "b1:b2" , { before : "line1\nline2" , after : "line1\nmodified" } ] ,
7693+ [ "b3:b4" , { before : "foo\nbar" , after : "foo\nbaz" } ] ,
7694+ ] ) ;
7695+ const multiDifferences = [
7696+ {
7697+ beforeBlob : { blobId : "b1" , path : "file1.ts" } ,
7698+ afterBlob : { blobId : "b2" , path : "file1.ts" } ,
7699+ } ,
7700+ {
7701+ beforeBlob : { blobId : "b3" , path : "file2.ts" } ,
7702+ afterBlob : { blobId : "b4" , path : "file2.ts" } ,
7703+ } ,
7704+ ] ;
7705+ const { stdin, lastFrame } = render (
7706+ < PullRequestDetail
7707+ pullRequest = { pullRequest as any }
7708+ differences = { multiDifferences as any }
7709+ commentThreads = { [ ] }
7710+ diffTexts = { multiFileDiffTexts }
7711+ onBack = { vi . fn ( ) }
7712+ onHelp = { vi . fn ( ) }
7713+ onShowActivity = { vi . fn ( ) }
7714+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7715+ inlineComment = { defaultInlineCommentProps }
7716+ reply = { defaultReplyProps }
7717+ approval = { defaultApprovalProps }
7718+ merge = { defaultMergeProps }
7719+ close = { defaultCloseProps }
7720+ commitView = { defaultCommitProps }
7721+ editComment = { defaultEditCommentProps }
7722+ deleteComment = { defaultDeleteCommentProps }
7723+ reaction = { defaultReactionProps }
7724+ terminalWidth = { 120 }
7725+ /> ,
7726+ ) ;
7727+ stdin . write ( "s" ) ;
7728+ await vi . waitFor ( ( ) => {
7729+ expect ( lastFrame ( ) ) . toContain ( "s unified" ) ;
7730+ } ) ;
7731+ stdin . write ( "n" ) ;
7732+ const output = lastFrame ( ) ?? "" ;
7733+ expect ( output ) . toContain ( "file2.ts" ) ;
7734+ } ) ;
7735+
7736+ it ( "scrolls with Ctrl+d in split mode" , async ( ) => {
7737+ const { stdin, lastFrame } = render (
7738+ < PullRequestDetail
7739+ pullRequest = { pullRequest as any }
7740+ differences = { differences as any }
7741+ commentThreads = { [ ] }
7742+ diffTexts = { diffTexts }
7743+ onBack = { vi . fn ( ) }
7744+ onHelp = { vi . fn ( ) }
7745+ onShowActivity = { vi . fn ( ) }
7746+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7747+ inlineComment = { defaultInlineCommentProps }
7748+ reply = { defaultReplyProps }
7749+ approval = { defaultApprovalProps }
7750+ merge = { defaultMergeProps }
7751+ close = { defaultCloseProps }
7752+ commitView = { defaultCommitProps }
7753+ editComment = { defaultEditCommentProps }
7754+ deleteComment = { defaultDeleteCommentProps }
7755+ reaction = { defaultReactionProps }
7756+ terminalWidth = { 120 }
7757+ /> ,
7758+ ) ;
7759+ stdin . write ( "s" ) ;
7760+ await vi . waitFor ( ( ) => {
7761+ expect ( lastFrame ( ) ) . toContain ( "s unified" ) ;
7762+ } ) ;
7763+ stdin . write ( "\x04" ) ; // Ctrl+d
7764+ const output = lastFrame ( ) ?? "" ;
7765+ expect ( output ) . toBeDefined ( ) ;
7766+ } ) ;
7767+
7768+ it ( "does not toggle split view during comment input" , async ( ) => {
7769+ const { stdin, lastFrame } = render (
7770+ < PullRequestDetail
7771+ pullRequest = { pullRequest as any }
7772+ differences = { differences as any }
7773+ commentThreads = { [ ] }
7774+ diffTexts = { diffTexts }
7775+ onBack = { vi . fn ( ) }
7776+ onHelp = { vi . fn ( ) }
7777+ onShowActivity = { vi . fn ( ) }
7778+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7779+ inlineComment = { defaultInlineCommentProps }
7780+ reply = { defaultReplyProps }
7781+ approval = { defaultApprovalProps }
7782+ merge = { defaultMergeProps }
7783+ close = { defaultCloseProps }
7784+ commitView = { defaultCommitProps }
7785+ editComment = { defaultEditCommentProps }
7786+ deleteComment = { defaultDeleteCommentProps }
7787+ reaction = { defaultReactionProps }
7788+ terminalWidth = { 120 }
7789+ /> ,
7790+ ) ;
7791+ stdin . write ( "c" ) ;
7792+ await vi . waitFor ( ( ) => {
7793+ expect ( lastFrame ( ) ) . toContain ( "Comment:" ) ;
7794+ } ) ;
7795+ stdin . write ( "s" ) ;
7796+ expect ( lastFrame ( ) ) . toContain ( "Comment:" ) ;
7797+ } ) ;
7798+
7799+ it ( "defaults to 120 width when terminalWidth prop is not provided" , async ( ) => {
7800+ const { stdin, lastFrame } = render (
7801+ < PullRequestDetail
7802+ pullRequest = { pullRequest as any }
7803+ differences = { differences as any }
7804+ commentThreads = { [ ] }
7805+ diffTexts = { diffTexts }
7806+ onBack = { vi . fn ( ) }
7807+ onHelp = { vi . fn ( ) }
7808+ onShowActivity = { vi . fn ( ) }
7809+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7810+ inlineComment = { defaultInlineCommentProps }
7811+ reply = { defaultReplyProps }
7812+ approval = { defaultApprovalProps }
7813+ merge = { defaultMergeProps }
7814+ close = { defaultCloseProps }
7815+ commitView = { defaultCommitProps }
7816+ editComment = { defaultEditCommentProps }
7817+ deleteComment = { defaultDeleteCommentProps }
7818+ reaction = { defaultReactionProps }
7819+ /> ,
7820+ ) ;
7821+ // No terminalWidth prop → falls back to stdout?.columns ?? 120
7822+ // Since ink-testing-library has no real stdout, uses 120 which is >= 100
7823+ expect ( lastFrame ( ) ) . toContain ( "s split" ) ;
7824+ stdin . write ( "s" ) ;
7825+ await vi . waitFor ( ( ) => {
7826+ expect ( lastFrame ( ) ) . toContain ( "s unified" ) ;
7827+ } ) ;
7828+ } ) ;
7829+ } ) ;
7830+
7831+ describe ( "navigation guards with empty diff" , ( ) => {
7832+ it ( "handles Ctrl+d, Ctrl+u, and G when lines are empty" , ( ) => {
7833+ const { stdin, lastFrame } = render (
7834+ < PullRequestDetail
7835+ pullRequest = { pullRequest as any }
7836+ differences = { [ ] }
7837+ commentThreads = { [ ] }
7838+ diffTexts = { new Map ( ) }
7839+ onBack = { vi . fn ( ) }
7840+ onHelp = { vi . fn ( ) }
7841+ onShowActivity = { vi . fn ( ) }
7842+ comment = { { onPost : vi . fn ( ) , isProcessing : false , error : null , onClearError : vi . fn ( ) } }
7843+ inlineComment = { defaultInlineCommentProps }
7844+ reply = { defaultReplyProps }
7845+ approval = { defaultApprovalProps }
7846+ merge = { defaultMergeProps }
7847+ close = { defaultCloseProps }
7848+ commitView = { defaultCommitProps }
7849+ editComment = { defaultEditCommentProps }
7850+ deleteComment = { defaultDeleteCommentProps }
7851+ reaction = { defaultReactionProps }
7852+ terminalWidth = { 120 }
7853+ /> ,
7854+ ) ;
7855+ // These should hit early return guards (lines.length === 0)
7856+ stdin . write ( "\x04" ) ; // Ctrl+d
7857+ stdin . write ( "\x15" ) ; // Ctrl+u
7858+ stdin . write ( "G" ) ;
7859+ // Component should still render without crash
7860+ expect ( lastFrame ( ) ) . toBeDefined ( ) ;
7861+ } ) ;
7862+ } ) ;
75617863} ) ;
0 commit comments