@@ -3,6 +3,8 @@ import { act, fireEvent, renderWithExtensionState } from "@/utils/test-utils"
33
44import type { ClineMessage } from "@roo-code/types"
55
6+ import { vscode } from "@src/utils/vscode"
7+
68import ChatView , { type ChatViewProps } from "../ChatView"
79
810type FollowOutput = ( ( isAtBottom : boolean ) => "auto" | false ) | "auto" | false
@@ -98,7 +100,9 @@ vi.mock("@/components/ui", async (importOriginal) => {
98100 const actual = await importOriginal < typeof import ( "@/components/ui" ) > ( )
99101 return {
100102 ...actual ,
101- StandardTooltip : ( { children } : { children : React . ReactNode } ) => < > { children } < /> ,
103+ StandardTooltip : ( { children, content } : { children : React . ReactNode ; content ?: string } ) => (
104+ < div data-tooltip = { content } > { children } </ div >
105+ ) ,
102106 }
103107} )
104108
@@ -357,9 +361,18 @@ const getScrollToCheckpointButton = (): HTMLButtonElement => {
357361 return button
358362}
359363
364+ const getButtonByText = ( text : string ) : HTMLButtonElement => {
365+ const button = Array . from ( document . querySelectorAll ( "button" ) ) . find ( ( candidate ) => candidate . textContent === text )
366+ if ( ! ( button instanceof HTMLButtonElement ) ) {
367+ throw new Error ( `Expected button with text: ${ text } ` )
368+ }
369+ return button
370+ }
371+
360372describe ( "ChatView scroll behavior regression coverage" , ( ) => {
361373 beforeEach ( ( ) => {
362374 vi . useFakeTimers ( )
375+ vi . mocked ( vscode . postMessage ) . mockClear ( )
363376 harness . scrollCalls = 0
364377 harness . scrollToIndexArgs = [ ]
365378 harness . atBottomAfterCalls = Number . POSITIVE_INFINITY
@@ -502,6 +515,78 @@ describe("ChatView scroll behavior regression coverage", () => {
502515 await expectChevronVisible ( )
503516 } )
504517
518+ it ( "keeps pending approval controls visible while browsing history" , async ( ) => {
519+ const baseTs = Date . now ( ) - 3_000
520+ const initialMessages = buildMessagesWithCheckpoint ( baseTs )
521+ await hydrate ( 2 , initialMessages )
522+ await waitForCalls ( 2 )
523+ await waitForCallsSettled ( )
524+
525+ const scrollable = getScrollable ( )
526+ await act ( async ( ) => {
527+ fireEvent . wheel ( scrollable , { deltaY : - 120 } )
528+ } )
529+ await expectChevronVisible ( )
530+
531+ await act ( async ( ) => {
532+ postState ( [
533+ ...initialMessages ,
534+ {
535+ type : "ask" ,
536+ ask : "tool" ,
537+ ts : baseTs + 4 ,
538+ text : JSON . stringify ( { tool : "finishTask" } ) ,
539+ } ,
540+ ] )
541+ } )
542+ await flushEffects ( )
543+
544+ expect ( document . querySelector ( ".codicon-chevron-down" ) ) . toBeTruthy ( )
545+ expect ( document . querySelector ( "button[aria-label='chat:scrollToLatestCheckpoint']" ) ) . toBeNull ( )
546+ const completeButton = getButtonByText ( "chat:completeSubtaskAndReturn" )
547+ expect ( completeButton . disabled ) . toBe ( false )
548+ expect ( completeButton . closest ( "[data-tooltip]" ) ?. getAttribute ( "data-tooltip" ) ) . toBe (
549+ "chat:completeSubtaskAndReturn" ,
550+ )
551+
552+ fireEvent . click ( completeButton )
553+ expect ( vscode . postMessage ) . toHaveBeenCalledWith ( { type : "askResponse" , askResponse : "yesButtonClicked" } )
554+ } )
555+
556+ it ( "keeps two-button approvals actionable while browsing history" , async ( ) => {
557+ const baseTs = Date . now ( ) - 3_000
558+ await hydrate ( 2 , buildMessages ( baseTs ) )
559+ await waitForCalls ( 2 )
560+ await waitForCallsSettled ( )
561+
562+ await act ( async ( ) => {
563+ fireEvent . wheel ( getScrollable ( ) , { deltaY : - 120 } )
564+ postState ( [
565+ ...buildMessages ( baseTs ) ,
566+ {
567+ type : "ask" ,
568+ ask : "tool" ,
569+ ts : baseTs + 3 ,
570+ text : JSON . stringify ( { tool : "editedExistingFile" , batchDiffs : [ ] } ) ,
571+ } ,
572+ ] )
573+ } )
574+ await flushEffects ( )
575+
576+ expect ( document . querySelector ( ".codicon-chevron-down" ) ) . toBeTruthy ( )
577+ const approveButton = getButtonByText ( "chat:edit-batch.approve.title" )
578+ const denyButton = getButtonByText ( "chat:edit-batch.deny.title" )
579+ expect ( approveButton . disabled ) . toBe ( false )
580+ expect ( denyButton . disabled ) . toBe ( false )
581+ expect ( approveButton . closest ( "[data-tooltip]" ) ?. getAttribute ( "data-tooltip" ) ) . toBe (
582+ "chat:edit-batch.approve.title" ,
583+ )
584+ expect ( denyButton . closest ( "[data-tooltip]" ) ?. getAttribute ( "data-tooltip" ) ) . toBe ( "chat:edit-batch.deny.title" )
585+
586+ fireEvent . click ( denyButton )
587+ expect ( vscode . postMessage ) . toHaveBeenCalledWith ( { type : "askResponse" , askResponse : "noButtonClicked" } )
588+ } )
589+
505590 it ( "hydration completion cannot override user escape hatch" , async ( ) => {
506591 await hydrate ( Number . POSITIVE_INFINITY )
507592 await waitForCalls ( 1 )
0 commit comments