diff --git a/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.logs-and-events.spec.ts b/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.logs-and-events.spec.ts index e106cf996b..9d74da29e9 100644 --- a/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.logs-and-events.spec.ts +++ b/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.logs-and-events.spec.ts @@ -116,17 +116,20 @@ describe('CollaborationManager logs and event helpers', () => { }), } - let payload: { nodes: Node[], edges: Edge[] } | null = null + const graphPayloads: Array<{ nodes: Node[], edges: Edge[] }> = [] manager.onGraphImport((graph) => { - payload = graph + graphPayloads.push(graph) }) manager.refreshGraphSynchronously() - expect(payload).not.toBeNull() - expect(payload?.nodes).toHaveLength(1) - expect(payload?.edges).toHaveLength(1) - expect(payload?.nodes[0]?.data.selected).toBe(true) + expect(graphPayloads).toHaveLength(1) + const payload = graphPayloads[0] + if (!payload) + throw new Error('graph import payload should exist') + expect(payload.nodes).toHaveLength(1) + expect(payload.edges).toHaveLength(1) + expect(payload.nodes[0]?.data.selected).toBe(true) }) it('clearGraphImportLog clears logs and pending import snapshot', () => { diff --git a/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.socket-and-subscriptions.spec.ts b/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.socket-and-subscriptions.spec.ts index 3881b53169..536e6d39aa 100644 --- a/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.socket-and-subscriptions.spec.ts +++ b/web/app/components/workflow/collaboration/core/__tests__/collaboration-manager.socket-and-subscriptions.spec.ts @@ -25,6 +25,14 @@ type LoroSubscribeEvent = { by?: string } +type UndoManagerLike = { + canUndo: () => boolean + canRedo: () => boolean + undo: () => boolean + redo: () => boolean + clear: () => void +} + type MockSocket = { id: string connected: boolean @@ -38,12 +46,14 @@ type CollaborationManagerInternals = { doc: LoroDoc | null nodesMap: LoroMap | null edgesMap: LoroMap | null + undoManager: UndoManagerLike | null activeConnections: Set currentAppId: string | null reactFlowStore: ReactFlowStore | null eventEmitter: { emit: (event: string, ...args: unknown[]) => void } + isUndoRedoInProgress: boolean isLeader: boolean leaderId: string | null pendingInitialSync: boolean @@ -444,11 +454,11 @@ describe('CollaborationManager socket and subscription behavior', () => { let reactFlowNodes: Node[] = [{ ...initialNode, - data: { + data: ({ ...initialNode.data, selected: true, _localMeta: 'keep-me', - }, + } as Node['data'] & Record), }] let reactFlowEdges: Edge[] = [edge] const setNodesSpy = vi.fn((nodes: Node[]) => { @@ -466,8 +476,8 @@ describe('CollaborationManager socket and subscription behavior', () => { }), } - let nodesSubscribeHandler: ((event: LoroSubscribeEvent) => void) | null = null - let edgesSubscribeHandler: ((event: LoroSubscribeEvent) => void) | null = null + let nodesSubscribeHandler: (event: LoroSubscribeEvent) => void = () => {} + let edgesSubscribeHandler: (event: LoroSubscribeEvent) => void = () => {} vi.spyOn(internals.nodesMap as object as { subscribe: (handler: (event: LoroSubscribeEvent) => void) => void }, 'subscribe') .mockImplementation((handler: (event: LoroSubscribeEvent) => void) => { nodesSubscribeHandler = handler @@ -477,20 +487,23 @@ describe('CollaborationManager socket and subscription behavior', () => { edgesSubscribeHandler = handler }) - let importedGraph: { nodes: Node[], edges: Edge[] } | null = null + const importedGraphs: Array<{ nodes: Node[], edges: Edge[] }> = [] manager.onGraphImport((payload) => { - importedGraph = payload + importedGraphs.push(payload) }) internals.setupSubscriptions() - nodesSubscribeHandler?.({ by: 'local' }) - nodesSubscribeHandler?.({ by: 'import' }) - edgesSubscribeHandler?.({ by: 'import' }) + nodesSubscribeHandler({ by: 'local' }) + nodesSubscribeHandler({ by: 'import' }) + edgesSubscribeHandler({ by: 'import' }) expect(setNodesSpy).toHaveBeenCalled() expect(setEdgesSpy).toHaveBeenCalled() - expect(importedGraph).not.toBeNull() - expect(importedGraph?.nodes[0]?.data).toMatchObject({ + expect(importedGraphs.length).toBeGreaterThan(0) + const importedGraph = importedGraphs.at(-1) + if (!importedGraph) + throw new Error('imported graph should exist') + expect(importedGraph.nodes[0]?.data).toMatchObject({ title: 'RemoteTitle', selected: true, _localMeta: 'keep-me', @@ -501,7 +514,7 @@ describe('CollaborationManager socket and subscription behavior', () => { expect(internals.pendingGraphImportEmit).toBe(true) internals.reactFlowStore = null - nodesSubscribeHandler?.({ by: 'import' }) + nodesSubscribeHandler({ by: 'import' }) rafSpy.mockRestore() }) @@ -944,8 +957,8 @@ describe('CollaborationManager socket and subscription behavior', () => { } internals.reactFlowStore = reactFlowStore - let nodesHandler: ((event: LoroSubscribeEvent) => void) | null = null - let edgesHandler: ((event: LoroSubscribeEvent) => void) | null = null + let nodesHandler: (event: LoroSubscribeEvent) => void = () => {} + let edgesHandler: (event: LoroSubscribeEvent) => void = () => {} vi.spyOn(internals.nodesMap as object as { subscribe: (handler: (event: LoroSubscribeEvent) => void) => void }, 'subscribe') .mockImplementation((handler: (event: LoroSubscribeEvent) => void) => { nodesHandler = handler @@ -957,13 +970,13 @@ describe('CollaborationManager socket and subscription behavior', () => { internals.setupSubscriptions() internals.isUndoRedoInProgress = true - nodesHandler?.({ by: 'import' }) - edgesHandler?.({ by: 'import' }) + nodesHandler({ by: 'import' }) + edgesHandler({ by: 'import' }) internals.isUndoRedoInProgress = false - edgesHandler?.({ by: 'local' }) + edgesHandler({ by: 'local' }) internals.reactFlowStore = null - edgesHandler?.({ by: 'import' }) + edgesHandler({ by: 'import' }) }) it('covers missing-doc guards and unauthorized rejoin early returns', () => { diff --git a/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts b/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts index b40befe35a..0f8a9e2c9a 100644 --- a/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts +++ b/web/app/components/workflow/collaboration/hooks/__tests__/use-collaboration.spec.ts @@ -38,9 +38,9 @@ vi.mock('../../core/collaboration-manager', () => ({ collaborationManager: { connect: (...args: unknown[]) => mockConnect(...args), disconnect: (...args: unknown[]) => mockDisconnect(...args), - isConnected: (...args: unknown[]) => mockIsConnected(...args), + isConnected: () => mockIsConnected(), emitCursorMove: (...args: unknown[]) => mockEmitCursorMove(...args), - getLeaderId: (...args: unknown[]) => mockGetLeaderId(...args), + getLeaderId: () => mockGetLeaderId(), onStateChange: (callback: (state: { isConnected?: boolean, disconnectReason?: string, error?: string }) => void) => { onStateChangeCallback = callback return unsubscribeState @@ -99,7 +99,7 @@ describe('useCollaboration', () => { }) onStateChangeCallback?.({ isConnected: true }) - onUsersCallback?.([{ user_id: 'u1', user_name: 'U1', avatar_url: '', sid: 'sid-1' } as OnlineUser]) + onUsersCallback?.([{ user_id: 'u1', username: 'U1', avatar: '', sid: 'sid-1' } as OnlineUser]) onCursorCallback?.({ u1: { x: 10, y: 20, userId: 'u1', timestamp: 1 } }) onPresenceCallback?.({ nodeA: { sid1: { userId: 'u1', username: 'U1', clientId: 'sid1', timestamp: 1 } } }) onLeaderCallback?.(true) diff --git a/web/app/components/workflow/comment/thread.spec.tsx b/web/app/components/workflow/comment/thread.spec.tsx index b55f51825b..fde972c67d 100644 --- a/web/app/components/workflow/comment/thread.spec.tsx +++ b/web/app/components/workflow/comment/thread.spec.tsx @@ -141,8 +141,6 @@ const createComment = (): WorkflowCommentDetail => ({ avatar_url: 'alice.png', }, created_at: 2, - updated_at: 2, - mentions: [], }], }) diff --git a/web/app/components/workflow/hooks/__tests__/use-leader-restore.spec.ts b/web/app/components/workflow/hooks/__tests__/use-leader-restore.spec.ts index f96d95cf7e..5837f1484d 100644 --- a/web/app/components/workflow/hooks/__tests__/use-leader-restore.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-leader-restore.spec.ts @@ -1,6 +1,8 @@ import type { RestoreIntentData, RestoreRequestData } from '../../collaboration/types/collaboration' import type { SyncDraftCallback } from '../../hooks-store/store' +import type { Edge, Node } from '../../types' import { act, renderHook } from '@testing-library/react' +import { ChatVarType } from '../../panel/chat-variable-panel/type' import { useLeaderRestore, useLeaderRestoreListener } from '../use-leader-restore' const mockSetViewport = vi.hoisted(() => vi.fn()) @@ -19,8 +21,8 @@ const mockGetIsLeader = vi.hoisted(() => vi.fn()) const mockSetNodes = vi.hoisted(() => vi.fn()) const mockSetEdges = vi.hoisted(() => vi.fn()) const mockRefreshGraphSynchronously = vi.hoisted(() => vi.fn()) -const mockGetNodes = vi.hoisted(() => vi.fn(() => [{ id: 'old-node' }])) -const mockGetEdges = vi.hoisted(() => vi.fn(() => [{ id: 'old-edge' }])) +const mockGetNodes = vi.hoisted(() => vi.fn(() => [{ id: 'old-node' } as unknown as Node])) +const mockGetEdges = vi.hoisted(() => vi.fn(() => [{ id: 'old-edge' } as unknown as Edge])) let restoreCompleteCallback: ((data: { versionId: string, success: boolean }) => void) | null = null let restoreRequestCallback: ((data: RestoreRequestData) => void) | null = null @@ -101,8 +103,8 @@ vi.mock('../../collaboration/core/collaboration-manager', () => ({ setNodes: (...args: unknown[]) => mockSetNodes(...args), setEdges: (...args: unknown[]) => mockSetEdges(...args), refreshGraphSynchronously: (...args: unknown[]) => mockRefreshGraphSynchronously(...args), - getNodes: (...args: unknown[]) => mockGetNodes(...args), - getEdges: (...args: unknown[]) => mockGetEdges(...args), + getNodes: () => mockGetNodes(), + getEdges: () => mockGetEdges(), onRestoreComplete: (callback: (data: { versionId: string, success: boolean }) => void) => { restoreCompleteCallback = callback return unsubscribeRestoreComplete @@ -124,12 +126,12 @@ describe('useLeaderRestore', () => { versionName: 'Version One', initiatorUserId: 'u-1', initiatorName: 'Alice', - features: { a: true }, - environmentVariables: [{ id: 'env-1', name: 'A', value: '1', value_type: 'string', description: '' }], - conversationVariables: [{ id: 'conv-1', name: 'B', value: '2', value_type: 'string', description: '' }], + features: { moreLikeThis: { enabled: true } }, + environmentVariables: [{ id: 'env-1', name: 'A', value: '1', value_type: ChatVarType.String, description: '' }], + conversationVariables: [{ id: 'conv-1', name: 'B', value: '2', value_type: ChatVarType.String, description: '' }], graphData: { - nodes: [{ id: 'new-node' }], - edges: [{ id: 'new-edge' }], + nodes: [{ id: 'new-node' } as unknown as Node], + edges: [{ id: 'new-edge' } as unknown as Edge], viewport: { x: 1, y: 2, zoom: 0.5 }, }, } @@ -163,7 +165,7 @@ describe('useLeaderRestore', () => { versionId: 'v-1', initiatorName: 'Alice', })) - expect(mockSetFeatures).toHaveBeenCalledWith({ a: true }) + expect(mockSetFeatures).toHaveBeenCalledWith({ moreLikeThis: { enabled: true } }) expect(mockSetEnvironmentVariables).toHaveBeenCalled() expect(mockSetConversationVariables).toHaveBeenCalled() expect(mockSetNodes).toHaveBeenCalledWith([{ id: 'old-node' }], [{ id: 'new-node' }], 'leader-restore:apply-graph') @@ -244,6 +246,7 @@ describe('useLeaderRestoreListener', () => { restoreIntentCallback?.({ versionId: 'v-3', versionName: 'Version Three', + initiatorUserId: 'u-3', initiatorName: 'Carol', }) }) diff --git a/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts b/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts index f7f05c62c8..2310df53b7 100644 --- a/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts @@ -579,6 +579,7 @@ describe('useNodesInteractions', () => { result.current.handleNodeDrag( { stopPropagation: vi.fn() } as never, currentNodes[0] as Node, + currentNodes as Node[], ) result.current.handleNodeSelect('drag-node-2') result.current.handleNodeConnect({ diff --git a/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts b/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts index 166fa05f71..5745603a5e 100644 --- a/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts @@ -285,7 +285,7 @@ describe('useWorkflowComment', () => { position_x: 50, position_y: 80, } - mockGetNodes.mockReturnValue([{ id: 'node-1', data: { selected: true } }]) + mockGetNodes.mockReturnValue([{ id: 'node-1', data: { selected: true } }] as never) mockFetchWorkflowComment.mockResolvedValue({ ...baseCommentDetail(), id: commentB.id, diff --git a/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx b/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx index 4c295dc94b..fcb93b68f2 100644 --- a/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx @@ -13,23 +13,29 @@ const mockEmitCommentsUpdate = vi.hoisted(() => vi.fn()) const commentFixtures: WorkflowCommentList[] = [ { id: 'c-1', + position_x: 10, + position_y: 20, created_by: 'user-1', - created_by_account: { id: 'user-1', name: 'Alice', avatar_url: '' }, + created_by_account: { id: 'user-1', name: 'Alice', email: 'alice@example.com', avatar_url: '' }, content: 'my open thread', created_at: 1, updated_at: 2, resolved: false, + mention_count: 0, reply_count: 2, participants: [], }, { id: 'c-2', + position_x: 30, + position_y: 40, created_by: 'user-2', - created_by_account: { id: 'user-2', name: 'Bob', avatar_url: '' }, + created_by_account: { id: 'user-2', name: 'Bob', email: 'bob@example.com', avatar_url: '' }, content: 'others resolved thread', created_at: 3, updated_at: 4, resolved: true, + mention_count: 0, reply_count: 0, participants: [], },