11/**
22 * @vitest -environment jsdom
33 */
4- import { act , type ReactNode , useImperativeHandle } from 'react'
4+ import { act , type ReactNode , StrictMode , useEffect , useImperativeHandle } from 'react'
5+ import { QueryClient , QueryClientProvider , useQueryClient } from '@tanstack/react-query'
56import { createRoot , type Root } from 'react-dom/client'
67import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
7- import type { FilePreviewSession } from '@/lib/copilot /request/session'
8+ import type { FilePreviewSession } from '@/lib/mothership /request/session'
89import type { FileDownloadSource } from '@/lib/uploads/client/download'
910import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
1011
@@ -43,6 +44,33 @@ vi.mock('@/app/workspace/[workspaceId]/files/components/file-viewer', () => ({
4344 } ,
4445} ) )
4546
47+ const logQueries = vi . hoisted ( ( ) => ( {
48+ detail : vi . fn ( ( ) => ( { data : undefined , isLoading : true , error : null } ) ) ,
49+ execution : vi . fn ( ( ) => ( { data : undefined , isLoading : true , error : null } ) ) ,
50+ } ) )
51+ vi . mock ( '@/hooks/queries/logs' , ( ) => ( {
52+ useLogDetail : logQueries . detail ,
53+ useLogByExecutionId : logQueries . execution ,
54+ } ) )
55+ const canvasHydration = vi . hoisted ( ( ) => ( { enabled : false , query : vi . fn ( ) , failed : vi . fn ( ) } ) )
56+ vi . mock ( '@/app/workspace/[workspaceId]/w/[workflowId]/workflow' , ( ) => ( {
57+ default : ( { workflowId } : { workflowId : string } ) => {
58+ const client = useQueryClient ( )
59+ useEffect ( ( ) => {
60+ if ( canvasHydration . enabled ) {
61+ void client
62+ . fetchQuery ( {
63+ queryKey : workflowKeys . state ( workflowId ) ,
64+ queryFn : canvasHydration . query ,
65+ staleTime : 0 ,
66+ } )
67+ . catch ( canvasHydration . failed )
68+ }
69+ } , [ client , workflowId ] )
70+ return < div > Verified workflow canvas</ div >
71+ } ,
72+ } ) )
73+
4674vi . mock ( '@/app/workspace/[workspaceId]/tables/[tableId]/table' , ( ) => ( {
4775 Table : ( ) => null ,
4876} ) )
@@ -60,35 +88,42 @@ import {
6088 ResourceContent ,
6189} from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content'
6290import type { MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
91+ import { workflowKeys } from '@/hooks/queries/utils/workflow-keys'
6392import { useTableViewPinStore } from '@/stores/table/view-pin/store'
6493
6594describe ( 'ResourceContent handoff' , ( ) => {
6695 let container : HTMLDivElement
6796 let root : Root
97+ let client : QueryClient
6898
6999 beforeEach ( ( ) => {
100+ vi . clearAllMocks ( )
101+ canvasHydration . enabled = false
70102 ; ( globalThis as { IS_REACT_ACT_ENVIRONMENT ?: boolean } ) . IS_REACT_ACT_ENVIRONMENT = true
71103 useTableViewPinStore . getState ( ) . reset ( )
72- vi . clearAllMocks ( )
73104 files . length = 0
74105 container = document . createElement ( 'div' )
75106 root = createRoot ( container )
107+ client = new QueryClient ( { defaultOptions : { queries : { retry : false } } } )
76108 } )
77109
78110 afterEach ( ( ) => {
79111 act ( ( ) => root . unmount ( ) )
80112 useTableViewPinStore . getState ( ) . reset ( )
113+ client . clear ( )
81114 } )
82115
83116 function render ( resource : MothershipResource ) {
84117 act ( ( ) => {
85118 root . render (
86119 (
87- < ResourceContent
88- workspaceId = 'workspace-1'
89- desktopScopeId = 'chat:chat-1'
90- resource = { resource }
91- />
120+ < QueryClientProvider client = { client } >
121+ < ResourceContent
122+ workspaceId = 'workspace-1'
123+ desktopScopeId = 'chat:chat-1'
124+ resource = { resource }
125+ />
126+ </ QueryClientProvider >
92127 ) as ReactNode
93128 )
94129 } )
@@ -103,6 +138,89 @@ describe('ResourceContent handoff', () => {
103138 expect ( useTableViewPinStore . getState ( ) . pins [ 'table-1' ] ?. viewId ) . toBe ( 'view-restored' )
104139 } )
105140
141+ it ( 'opens a CLI log by its execution ID instead of querying it as a storage row' , ( ) => {
142+ render ( { type : 'log' , id : 'run-1' , executionId : 'run-1' , title : 'Workflow run' } )
143+ expect ( logQueries . detail ) . toHaveBeenCalledWith ( 'run-1' , 'workspace-1' , { enabled : false } )
144+ expect ( logQueries . execution ) . toHaveBeenCalledWith ( 'workspace-1' , 'run-1' )
145+ } )
146+
147+ it ( 'keeps existing log row IDs on their ordinary detail path' , ( ) => {
148+ render ( { type : 'log' , id : 'row-1' , title : 'Workflow run' } )
149+ expect ( logQueries . detail ) . toHaveBeenCalledWith ( 'row-1' , 'workspace-1' , { enabled : true } )
150+ expect ( logQueries . execution ) . toHaveBeenCalledWith ( 'workspace-1' , undefined )
151+ } )
152+
153+ it ( 'opens a foreign-workspace read without adding it to this workspace sidebar' , ( ) => {
154+ client . setQueryData ( workflowKeys . list ( 'workspace-1' ) , [ ] )
155+ client . setQueryData ( workflowKeys . state ( 'foreign' ) , {
156+ id : 'foreign' ,
157+ workspaceId : 'workspace-2' ,
158+ name : 'Other workspace workflow' ,
159+ } )
160+ render ( { type : 'workflow' , id : 'foreign' , title : 'Other workspace workflow' } )
161+ expect ( container . textContent ) . toContain ( 'Other workspace workflow' )
162+ expect ( container . textContent ) . toContain ( 'Open in its workspace' )
163+ expect ( client . getQueryData ( workflowKeys . list ( 'workspace-1' ) ) ) . toEqual ( [ ] )
164+ } )
165+
166+ it ( 'uses authorized same-workspace metadata, including folder and order, for a missing inventory entry' , async ( ) => {
167+ client . setQueryData ( workflowKeys . list ( 'workspace-1' ) , [ ] )
168+ client . setQueryData ( workflowKeys . state ( 'verified' ) , {
169+ id : 'verified' ,
170+ workspaceId : 'workspace-1' ,
171+ name : 'Verified name' ,
172+ folderId : 'actual-folder' ,
173+ sortOrder : 42 ,
174+ createdAt : new Date ( '2026-09-01' ) ,
175+ updatedAt : new Date ( '2026-09-02' ) ,
176+ archivedAt : null ,
177+ } )
178+ render ( { type : 'workflow' , id : 'verified' , title : 'Untrusted placeholder' } )
179+ await act ( async ( ) => { } )
180+ expect ( client . getQueryData ( workflowKeys . list ( 'workspace-1' ) ) ) . toEqual ( [
181+ expect . objectContaining ( {
182+ id : 'verified' ,
183+ name : 'Verified name' ,
184+ folderId : 'actual-folder' ,
185+ sortOrder : 42 ,
186+ workspaceId : 'workspace-1' ,
187+ } ) ,
188+ ] )
189+ } )
190+
191+ it ( 'does not cancel canvas hydration when StrictMode replays panel effects' , async ( ) => {
192+ client . setQueryData ( workflowKeys . list ( 'workspace-1' ) , [ { id : 'verified' } ] )
193+ canvasHydration . enabled = true
194+ let resolveHydration ! : ( value : object ) => void
195+ let hydrationSignal : AbortSignal | undefined
196+ canvasHydration . query . mockImplementation ( ( { signal } : { signal : AbortSignal } ) => {
197+ hydrationSignal = signal
198+ return new Promise ( ( resolve ) => {
199+ resolveHydration = resolve
200+ } )
201+ } )
202+ await act ( async ( ) => {
203+ root . render (
204+ < StrictMode >
205+ < QueryClientProvider client = { client } >
206+ < ResourceContent
207+ workspaceId = 'workspace-1'
208+ desktopScopeId = 'chat:chat-1'
209+ resource = { { type : 'workflow' , id : 'verified' , title : 'Verified workflow' } }
210+ />
211+ </ QueryClientProvider >
212+ </ StrictMode >
213+ )
214+ } )
215+ expect ( canvasHydration . failed ) . not . toHaveBeenCalled ( )
216+ expect ( hydrationSignal ?. aborted ) . toBe ( false )
217+ await act ( async ( ) => {
218+ resolveHydration ( { id : 'verified' } )
219+ } )
220+ expect ( canvasHydration . failed ) . not . toHaveBeenCalled ( )
221+ expect ( container . textContent ) . toContain ( 'Verified workflow canvas' )
222+ } )
223+
106224 it ( 'does not pin a table opened without a saved view' , ( ) => {
107225 render ( { type : 'table' , id : 'table-1' , title : 'Invoices' } )
108226
0 commit comments