@@ -7,6 +7,7 @@ import { createRoot, type Root } from 'react-dom/client'
77import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
88
99const {
10+ chatStoreState,
1011 executionStoreState,
1112 mockCancel,
1213 mockExecute,
@@ -22,6 +23,9 @@ const {
2223 workflowBlocks,
2324 workflowStoreState,
2425} = vi . hoisted ( ( ) => {
26+ const chatStoreState = {
27+ selectedWorkflowOutputs : [ ] as string [ ] ,
28+ }
2529 const workflowBlocks = {
2630 start : {
2731 id : 'start' ,
@@ -82,6 +86,7 @@ const {
8286 }
8387
8488 return {
89+ chatStoreState,
8590 executionStoreState,
8691 mockCancel : vi . fn ( ) ,
8792 mockExecute : vi . fn ( ) ,
@@ -167,9 +172,18 @@ vi.mock('@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow
167172
168173vi . mock ( '@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils' , ( ) => ( {
169174 addHttpErrorConsoleEntry : vi . fn ( ) ,
170- createBlockEventHandlers : ( ) => ( {
175+ createBlockEventHandlers : ( config : {
176+ onBlockCompleteCallback ?: (
177+ blockId : string ,
178+ output : unknown ,
179+ blockExecutionId ?: string
180+ ) => Promise < void >
181+ } ) => ( {
171182 onBlockStarted : vi . fn ( ) ,
172- onBlockCompleted : vi . fn ( ) ,
183+ onBlockCompleted : vi . fn (
184+ ( data : { blockId : string ; output : unknown ; blockExecutionId ?: string } ) =>
185+ config . onBlockCompleteCallback ?.( data . blockId , data . output , data . blockExecutionId )
186+ ) ,
173187 onBlockError : vi . fn ( ) ,
174188 onBlockChildWorkflowStarted : vi . fn ( ) ,
175189 } ) ,
@@ -221,7 +235,7 @@ vi.mock('@/serializer', () => ({
221235vi . mock ( '@/stores/chat/store' , ( ) => ( {
222236 useChatStore : {
223237 getState : ( ) => ( {
224- getSelectedWorkflowOutput : ( ) => [ ] ,
238+ getSelectedWorkflowOutput : ( ) => chatStoreState . selectedWorkflowOutputs ,
225239 } ) ,
226240 } ,
227241} ) )
@@ -343,6 +357,7 @@ async function drainStream(value: unknown): Promise<void> {
343357describe ( 'useWorkflowExecution cancellation' , ( ) => {
344358 beforeEach ( ( ) => {
345359 vi . clearAllMocks ( )
360+ chatStoreState . selectedWorkflowOutputs = [ ]
346361 executionStoreState . getCurrentExecutionId . mockReturnValue ( 'execution-1' )
347362 mockRequestJson . mockResolvedValue ( { success : true } )
348363 } )
@@ -402,6 +417,7 @@ describe('useWorkflowExecution cancellation', () => {
402417describe ( 'useWorkflowExecution attachment uploads' , ( ) => {
403418 beforeEach ( ( ) => {
404419 vi . clearAllMocks ( )
420+ chatStoreState . selectedWorkflowOutputs = [ ]
405421 executionStoreState . getCurrentExecutionId . mockReturnValue ( null )
406422 mockResolveStartCandidates . mockReturnValue ( [ ] )
407423 mockSelectBestTrigger . mockReturnValue ( [ ] )
@@ -584,6 +600,52 @@ describe('useWorkflowExecution attachment uploads', () => {
584600 unmount ( )
585601 } )
586602
603+ it ( 'does not append a later sibling output after the block has streamed' , async ( ) => {
604+ chatStoreState . selectedWorkflowOutputs = [ 'agent-1_content' ]
605+ mockExecute . mockImplementationOnce ( async ( options ) => {
606+ options . onExecutionId ?.( 'execution-1' )
607+ await options . callbacks ?. onStreamChunk ?.( {
608+ blockId : 'agent-1' ,
609+ blockExecutionId : 'invoke-streamed' ,
610+ chunk : 'streamed answer' ,
611+ } )
612+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) )
613+ await options . callbacks ?. onBlockCompleted ?.( {
614+ blockId : 'agent-1' ,
615+ blockExecutionId : 'invoke-streamed' ,
616+ output : { content : 'streamed answer' } ,
617+ } )
618+ await options . callbacks ?. onBlockCompleted ?.( {
619+ blockId : 'agent-1' ,
620+ blockExecutionId : 'invoke-later' ,
621+ output : { content : 'later answer' } ,
622+ } )
623+ } )
624+
625+ const { result, unmount } = renderWorkflowExecutionHook ( )
626+ const decoder = new TextDecoder ( )
627+ let streamedText = ''
628+
629+ await act ( async ( ) => {
630+ const runResult = await result ( ) . handleRunWorkflow ( { input : 'chat input' } )
631+ if ( ! isChatWorkflowRunResult ( runResult ) ) {
632+ throw new Error ( 'Expected a chat workflow run result' )
633+ }
634+ const reader = runResult . stream . getReader ( )
635+ while ( true ) {
636+ const { done, value } = await reader . read ( )
637+ if ( done ) break
638+ streamedText += decoder . decode ( value , { stream : true } )
639+ }
640+ streamedText += decoder . decode ( )
641+ } )
642+
643+ expect ( streamedText ) . toContain ( 'streamed answer' )
644+ expect ( streamedText ) . not . toContain ( 'later answer' )
645+
646+ unmount ( )
647+ } )
648+
587649 it ( 'preserves legacy live thinking when no display projection field is sent' , async ( ) => {
588650 mockExecute . mockImplementationOnce ( async ( options ) => {
589651 options . onExecutionId ?.( 'execution-1' )
0 commit comments