fix: web style

This commit is contained in:
hjlarry 2026-04-11 19:35:04 +08:00
parent e5ab4d1ed3
commit 73c77b6701
8 changed files with 66 additions and 42 deletions

View File

@ -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', () => {

View File

@ -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<string>
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<string, unknown>),
}]
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', () => {

View File

@ -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)

View File

@ -141,8 +141,6 @@ const createComment = (): WorkflowCommentDetail => ({
avatar_url: 'alice.png',
},
created_at: 2,
updated_at: 2,
mentions: [],
}],
})

View File

@ -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',
})
})

View File

@ -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({

View File

@ -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,

View File

@ -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: [],
},