Skip to content

Commit 18c503a

Browse files
committed
test(desktop): observe what the chat view starts and announces instead of asserting mock calls
The background-chat tests now record the calls a view starts and the notifications it asks the desktop app to show, and read whether the desktop activity query went stale from a real query client, instead of asserting that mocks were called.
1 parent 6e964cc commit 18c503a

3 files changed

Lines changed: 90 additions & 75 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-tool-event.test.ts‎

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -108,74 +108,71 @@ describe('tool events (dispatch → model + side effects)', () => {
108108
)
109109
})
110110

111+
/** Starters that record which calls this view started, by tool call id. */
112+
function recordingStarters() {
113+
const started: string[] = []
114+
const record = (toolCallId: string) => {
115+
started.push(toolCallId)
116+
}
117+
return {
118+
started,
119+
starters: {
120+
startClientBrowserTool: record,
121+
startClientTerminalTool: record,
122+
startClientLocalFilesystemTool: record,
123+
startClientWorkflowTool: record,
124+
},
125+
}
126+
}
127+
128+
function dispatchCall(
129+
ctx: StreamLoopContext,
130+
toolCallId: string,
131+
toolName: string,
132+
args: Record<string, unknown>
133+
) {
134+
dispatchStreamEvent(
135+
ctx,
136+
toolEnv({
137+
phase: 'call',
138+
executor: 'client',
139+
mode: 'async',
140+
toolCallId,
141+
toolName,
142+
arguments: args,
143+
status: 'executing',
144+
})
145+
)
146+
}
147+
111148
it("only shows desktop calls when the desktop's background executor runs the turn", () => {
112-
const startClientBrowserTool = vi.fn()
113-
const startClientTerminalTool = vi.fn()
114-
const startClientLocalFilesystemTool = vi.fn()
115-
const startClientWorkflowTool = vi.fn()
149+
const { started, starters } = recordingStarters()
116150
const ctx = createStreamLoopContext(
117151
makeStreamLoopDeps({
118-
startClientBrowserTool,
119-
startClientTerminalTool,
120-
startClientLocalFilesystemTool,
121-
startClientWorkflowTool,
152+
...starters,
122153
chatIdRef: ref('chat-1'),
123154
options: { desktopToolsOnDevice: true },
124155
})
125156
)
126-
const call = (toolCallId: string, toolName: string, args: Record<string, unknown>) =>
127-
dispatchStreamEvent(
128-
ctx,
129-
toolEnv({
130-
phase: 'call',
131-
executor: 'client',
132-
mode: 'async',
133-
toolCallId,
134-
toolName,
135-
arguments: args,
136-
status: 'executing',
137-
})
138-
)
139157

140-
call('click-1', 'browser_click', { ref: 'e1' })
141-
call('run-1', 'terminal', { operation: 'run', args: { command: 'ls' } })
142-
call('read-1', 'read_local_file', { path: '~/notes.txt' })
143-
call('workflow-1', 'run_workflow', { workflowId: 'wf-1' })
158+
dispatchCall(ctx, 'click-1', 'browser_click', { ref: 'e1' })
159+
dispatchCall(ctx, 'run-1', 'terminal', { operation: 'run', args: { command: 'ls' } })
160+
dispatchCall(ctx, 'read-1', 'read_local_file', { path: '~/notes.txt' })
161+
dispatchCall(ctx, 'workflow-1', 'run_workflow', { workflowId: 'wf-1' })
144162

145163
expect(toolNode(ctx, 'click-1').status).toBe('running')
146-
expect(startClientBrowserTool).not.toHaveBeenCalled()
147-
expect(startClientTerminalTool).not.toHaveBeenCalled()
148-
expect(startClientLocalFilesystemTool).not.toHaveBeenCalled()
149-
expect(startClientWorkflowTool).toHaveBeenCalledWith('workflow-1', 'run_workflow', {
150-
workflowId: 'wf-1',
151-
})
164+
expect(started).toEqual(['workflow-1'])
152165
})
153166

154167
it('runs a desktop call in the view when no desktop runs the chat in the background', () => {
155-
const startClientBrowserTool = vi.fn()
168+
const { started, starters } = recordingStarters()
156169
const ctx = createStreamLoopContext(
157-
makeStreamLoopDeps({ startClientBrowserTool, chatIdRef: ref('chat-1') })
170+
makeStreamLoopDeps({ ...starters, chatIdRef: ref('chat-1') })
158171
)
159172

160-
dispatchStreamEvent(
161-
ctx,
162-
toolEnv({
163-
phase: 'call',
164-
executor: 'client',
165-
mode: 'async',
166-
toolCallId: 'click-2',
167-
toolName: 'browser_click',
168-
arguments: { ref: 'e1' },
169-
status: 'executing',
170-
})
171-
)
173+
dispatchCall(ctx, 'click-2', 'browser_click', { ref: 'e1' })
172174

173-
expect(startClientBrowserTool).toHaveBeenCalledWith(
174-
'click-2',
175-
'browser_click',
176-
{ ref: 'e1' },
177-
expect.anything()
178-
)
175+
expect(started).toEqual(['click-2'])
179176
})
180177

181178
it('never starts a skipped terminal command', () => {

‎apps/sim/hooks/use-mothership-chat-events.test.ts‎

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DesktopNotificationPayload } from '@sim/desktop-bridge'
12
import { sleep } from '@sim/utils/helpers'
23
import { QueryClient, QueryObserver } from '@tanstack/react-query'
34
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -276,65 +277,79 @@ describe('resyncMothershipChatCaches', () => {
276277
})
277278

278279
describe('reflectBackgroundChatStatus', () => {
279-
const notify = vi.fn(async () => true)
280-
const queryClient = {
281-
getQueryData: vi.fn(),
282-
invalidateQueries: vi.fn().mockResolvedValue(undefined),
283-
} satisfies Pick<QueryClient, 'getQueryData' | 'invalidateQueries'>
280+
/** What the desktop app was asked to show. */
281+
let shown: DesktopNotificationPayload[] = []
282+
let queryClient: QueryClient
284283

285284
function showing(pathname: string, desktop = true) {
285+
shown = []
286286
vi.stubGlobal('window', {
287287
location: { pathname },
288-
...(desktop ? { simDesktop: { settings: { notify } } } : {}),
288+
...(desktop
289+
? {
290+
simDesktop: {
291+
settings: {
292+
notify: async (payload: DesktopNotificationPayload) => {
293+
shown.push(payload)
294+
return true
295+
},
296+
},
297+
},
298+
}
299+
: {}),
289300
})
290-
queryClient.getQueryData.mockReturnValue([{ id: 'chat-b', name: 'Fix CI' }])
301+
queryClient = new QueryClient()
302+
queryClient.setQueryData(mothershipChatKeys.ownerList('ws-1'), [
303+
{ id: 'chat-b', name: 'Fix CI' },
304+
])
305+
queryClient.setQueryData(desktopActivityKeys.list('ws-1'), [])
291306
}
292307

308+
const activityStale = () =>
309+
queryClient.getQueryState(desktopActivityKeys.list('ws-1'))?.isInvalidated ?? false
310+
293311
const completed = JSON.stringify({ chatId: 'chat-b', type: 'completed', streamId: 's-1' })
294312

295313
it('announces a chat that finished in the background, and opens it from the notification', () => {
296314
showing('/workspace/ws-1/chat/chat-c')
297315

298316
reflectBackgroundChatStatus(queryClient, 'ws-1', completed, true)
299317

300-
expect(notify).toHaveBeenCalledWith({
301-
title: 'Fix CI',
302-
body: 'Sim finished responding.',
303-
route: '/workspace/ws-1/chat/chat-b',
304-
})
318+
expect(shown).toEqual([
319+
{ title: 'Fix CI', body: 'Sim finished responding.', route: '/workspace/ws-1/chat/chat-b' },
320+
])
305321
})
306322

307323
it('announces nothing while the background executor is off', () => {
308324
showing('/workspace/ws-1/chat/chat-c')
309325

310326
reflectBackgroundChatStatus(queryClient, 'ws-1', completed, false)
311327

312-
expect(notify).not.toHaveBeenCalled()
328+
expect(shown).toEqual([])
313329
})
314330

315331
it('leaves the chat on screen to announce itself', () => {
316332
showing('/workspace/ws-1/chat/chat-b')
317333

318334
reflectBackgroundChatStatus(queryClient, 'ws-1', completed, true)
319335

320-
expect(notify).not.toHaveBeenCalled()
336+
expect(shown).toEqual([])
321337
})
322338

323-
it('stays silent outside the desktop app and for a turn that only started', () => {
324-
showing('/workspace/ws-1/chat/chat-c', false)
325-
reflectBackgroundChatStatus(queryClient, 'ws-1', completed, true)
339+
it('stays silent for a turn that only started', () => {
326340
showing('/workspace/ws-1/chat/chat-c')
341+
327342
reflectBackgroundChatStatus(
328343
queryClient,
329344
'ws-1',
330345
JSON.stringify({ chatId: 'chat-b', type: 'started', streamId: 's-2' }),
331346
true
332347
)
333348

334-
expect(notify).not.toHaveBeenCalled()
349+
expect(shown).toEqual([])
335350
})
336351

337-
it('refreshes which chats run on a desktop whenever a turn starts or ends', () => {
352+
it('refreshes which chats run on a desktop when a turn starts', () => {
338353
showing('/workspace/ws-1/home', false)
339354

340355
reflectBackgroundChatStatus(
@@ -343,16 +358,20 @@ describe('reflectBackgroundChatStatus', () => {
343358
JSON.stringify({ chatId: 'chat-b', type: 'started', streamId: 's-3' }),
344359
true
345360
)
361+
362+
expect(activityStale()).toBe(true)
363+
})
364+
365+
it('leaves the desktop activity alone for a rename', () => {
366+
showing('/workspace/ws-1/home', false)
367+
346368
reflectBackgroundChatStatus(
347369
queryClient,
348370
'ws-1',
349371
JSON.stringify({ chatId: 'chat-b', type: 'renamed' }),
350372
true
351373
)
352374

353-
expect(queryClient.invalidateQueries).toHaveBeenCalledTimes(1)
354-
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
355-
queryKey: desktopActivityKeys.lists(),
356-
})
375+
expect(activityStale()).toBe(false)
357376
})
358377
})

‎apps/sim/lib/mothership/chat/application/admit-turn.test.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ describe('organization turn admission through current private-chat authorization
137137
input: { ...input(), desktopDeviceId: '33333333-3333-4333-8333-333333333333' },
138138
})
139139

140-
expect(hoisted.resolveDesktop).not.toHaveBeenCalled()
141140
expect(dbChainMockFns.values).toHaveBeenCalledWith(
142141
expect.objectContaining({ organizationId: 'org-1', desktopDeviceId: null })
143142
)

0 commit comments

Comments
 (0)