@@ -4,7 +4,7 @@ import { describe, it, expect, vi } from "vitest"
44import { ClineProvider } from "../core/webview/ClineProvider"
55import { makeProviderStub } from "./helpers/provider-stub"
66
7- // After the refactor: removeClineFromStack() is pure lifecycle — it pops , aborts, and
7+ // After the refactor: removeClineFromStack() is pure lifecycle — it removes the focused task , aborts, and
88// cleans up listeners. It does NOT mutate delegation metadata. All delegated→active
99// transitions are owned by reopenParentFromDelegation() (normal child completion) or
1010// markDelegatedChildInterrupted() (live eviction via navigation / new-task / clear).
@@ -49,17 +49,47 @@ function buildMockProvider(opts: {
4949}
5050
5151describe ( "ClineProvider.removeClineFromStack() — pure lifecycle, no delegation side effects" , ( ) => {
52- it ( "pops the task, aborts it, and clears listeners" , async ( ) => {
52+ it ( "removes the focused task, aborts it, and clears listeners" , async ( ) => {
5353 const { provider, childTask } = buildMockProvider ( { childTaskId : "child-1" } )
54- expect ( provider . clineStack ) . toHaveLength ( 1 )
54+ expect ( ( provider as any ) . taskRegistry . length ) . toBe ( 1 )
5555
5656 await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
5757
58- expect ( provider . clineStack ) . toHaveLength ( 0 )
58+ expect ( ( provider as any ) . taskRegistry . length ) . toBe ( 0 )
5959 expect ( childTask . abortTask ) . toHaveBeenCalledWith ( true )
6060 expect ( childTask . emit ) . toHaveBeenCalledWith ( expect . stringContaining ( "taskUnfocused" ) )
6161 } )
6262
63+ it ( "removes the focused task even when it is not the top stack entry" , async ( ) => {
64+ const focusedTask = {
65+ taskId : "focused-1" ,
66+ instanceId : "focused-inst" ,
67+ emit : vi . fn ( ) ,
68+ abortTask : vi . fn ( ) . mockResolvedValue ( undefined ) ,
69+ }
70+ const topTask = {
71+ taskId : "top-1" ,
72+ instanceId : "top-inst" ,
73+ emit : vi . fn ( ) ,
74+ abortTask : vi . fn ( ) . mockResolvedValue ( undefined ) ,
75+ }
76+ const provider = makeProviderStub ( {
77+ tasks : [ focusedTask , topTask ] as any [ ] ,
78+ taskEventListeners : new Map ( ) ,
79+ log : vi . fn ( ) ,
80+ getTaskWithId : vi . fn ( ) ,
81+ updateTaskHistory : vi . fn ( ) ,
82+ } )
83+ ; ( provider as any ) . taskRegistry . setCurrent ( "focused-1" )
84+
85+ await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
86+
87+ expect ( ( provider as any ) . taskRegistry . taskIds ) . toEqual ( [ "top-1" ] )
88+ expect ( ( provider as any ) . taskRegistry . current ) . toBe ( topTask )
89+ expect ( focusedTask . abortTask ) . toHaveBeenCalledWith ( true )
90+ expect ( topTask . abortTask ) . not . toHaveBeenCalled ( )
91+ } )
92+
6393 it ( "does NOT mutate parent metadata when a delegated child is popped (repair removed)" , async ( ) => {
6494 const { provider, updateTaskHistory, getTaskWithId } = buildMockProvider ( {
6595 childTaskId : "child-1" ,
@@ -74,7 +104,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
74104
75105 await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
76106
77- expect ( provider . clineStack ) . toHaveLength ( 0 )
107+ expect ( ( provider as any ) . taskRegistry . length ) . toBe ( 0 )
78108 // Navigation/disposal must never silently flip the parent to active
79109 expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
80110 expect ( updateTaskHistory ) . not . toHaveBeenCalled ( )
@@ -94,7 +124,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
94124
95125 await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
96126
97- expect ( provider . clineStack ) . toHaveLength ( 0 )
127+ expect ( ( provider as any ) . taskRegistry . length ) . toBe ( 0 )
98128 expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
99129 expect ( updateTaskHistory ) . not . toHaveBeenCalled ( )
100130 } )
@@ -106,7 +136,7 @@ describe("ClineProvider.removeClineFromStack() — pure lifecycle, no delegation
106136
107137 await ( ClineProvider . prototype as any ) . removeClineFromStack . call ( provider )
108138
109- expect ( provider . clineStack ) . toHaveLength ( 0 )
139+ expect ( ( provider as any ) . taskRegistry . length ) . toBe ( 0 )
110140 expect ( getTaskWithId ) . not . toHaveBeenCalled ( )
111141 expect ( updateTaskHistory ) . not . toHaveBeenCalled ( )
112142 } )
@@ -490,7 +520,7 @@ describe("ClineProvider.evictCurrentTask() — active delegated child path", ()
490520
491521 await ( ClineProvider . prototype as any ) . evictCurrentTask . call ( provider )
492522
493- expect ( provider . clineStack ) . toHaveLength ( 0 )
523+ expect ( ( provider as any ) . taskRegistry . length ) . toBe ( 0 )
494524 expect ( markDelegatedChildInterrupted ) . toHaveBeenCalledWith ( { childTaskId, parentTaskId } )
495525 } )
496526
0 commit comments