* add platform guards * add service management * fix types * move to zustand for servicehub * update App Updater * update tauri missing move * update app updater * refactor: move PlatformFeatures to separate const file 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * change tauri fetch name * update implementation * update extension fetch * make web version run properly * disabled unused web settings * fix all tests * fix lint * fix tests * add mock for extension * fix build * update make and mise * fix tsconfig for web-extensions * fix loader type * cleanup * fix test * update error handling + mcp should be working * Update mcp init * use separate is_web_app build property * Remove fixed model catalog url * fix additional tests * fix download issue (event emitter not implemented correctly) * Update Title html * fix app logs * update root tsx render timing --------- Co-authored-by: Claude <noreply@anthropic.com>
184 lines
5.0 KiB
TypeScript
184 lines
5.0 KiB
TypeScript
import { renderHook, act } from '@testing-library/react'
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { SystemEvent } from '@/types/events'
|
|
|
|
// Mock functions
|
|
const mockGetTools = vi.fn()
|
|
const mockUpdateTools = vi.fn()
|
|
const mockListen = vi.fn()
|
|
const mockUnsubscribe = vi.fn()
|
|
|
|
// Mock useAppState
|
|
vi.mock('../useAppState', () => ({
|
|
useAppState: () => ({
|
|
updateTools: mockUpdateTools,
|
|
}),
|
|
}))
|
|
|
|
// Mock the ServiceHub
|
|
vi.mock('@/hooks/useServiceHub', () => ({
|
|
getServiceHub: () => ({
|
|
mcp: () => ({
|
|
getTools: mockGetTools,
|
|
}),
|
|
events: () => ({
|
|
listen: mockListen,
|
|
}),
|
|
}),
|
|
}))
|
|
|
|
describe('useTools', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockListen.mockResolvedValue(mockUnsubscribe)
|
|
mockGetTools.mockResolvedValue([])
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('should call getTools and updateTools on mount', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
const mockTools = [
|
|
{ name: 'test-tool', description: 'A test tool' },
|
|
{ name: 'another-tool', description: 'Another test tool' },
|
|
]
|
|
mockGetTools.mockResolvedValue(mockTools)
|
|
|
|
renderHook(() => useTools())
|
|
|
|
// Wait for async operations to complete
|
|
await act(async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(mockGetTools).toHaveBeenCalledTimes(1)
|
|
expect(mockUpdateTools).toHaveBeenCalledWith(mockTools)
|
|
})
|
|
|
|
it('should set up event listener for MCP_UPDATE', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
renderHook(() => useTools())
|
|
|
|
await act(async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(mockListen).toHaveBeenCalledWith(
|
|
SystemEvent.MCP_UPDATE,
|
|
expect.any(Function)
|
|
)
|
|
})
|
|
|
|
it('should call setTools when MCP_UPDATE event is triggered', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
const mockTools = [{ name: 'updated-tool', description: 'Updated tool' }]
|
|
mockGetTools.mockResolvedValue(mockTools)
|
|
|
|
let eventCallback: () => void
|
|
|
|
mockListen.mockImplementation((_event, callback) => {
|
|
eventCallback = callback
|
|
return Promise.resolve(mockUnsubscribe)
|
|
})
|
|
|
|
renderHook(() => useTools())
|
|
|
|
// Wait for initial setup
|
|
await act(async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
})
|
|
|
|
// Clear the initial calls
|
|
vi.clearAllMocks()
|
|
mockGetTools.mockResolvedValue(mockTools)
|
|
|
|
// Trigger the event
|
|
await act(async () => {
|
|
eventCallback()
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(mockGetTools).toHaveBeenCalledTimes(1)
|
|
expect(mockUpdateTools).toHaveBeenCalledWith(mockTools)
|
|
})
|
|
|
|
it('should return unsubscribe function for cleanup', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
const { unmount } = renderHook(() => useTools())
|
|
|
|
await act(async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
})
|
|
|
|
expect(mockListen).toHaveBeenCalled()
|
|
|
|
// Unmount should call the unsubscribe function
|
|
unmount()
|
|
|
|
expect(mockListen).toHaveBeenCalledWith(
|
|
SystemEvent.MCP_UPDATE,
|
|
expect.any(Function)
|
|
)
|
|
})
|
|
|
|
it('should handle getTools errors gracefully', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
mockGetTools.mockRejectedValue(new Error('Failed to get tools'))
|
|
|
|
renderHook(() => useTools())
|
|
|
|
await act(async () => {
|
|
// Give enough time for the promise to be handled
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
})
|
|
|
|
expect(mockGetTools).toHaveBeenCalledTimes(1)
|
|
// updateTools should not be called if getTools fails
|
|
expect(mockUpdateTools).not.toHaveBeenCalled()
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
|
|
it('should handle event listener setup errors gracefully', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
mockListen.mockRejectedValue(new Error('Failed to set up listener'))
|
|
|
|
renderHook(() => useTools())
|
|
|
|
await act(async () => {
|
|
// Give enough time for the promise to be handled
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
})
|
|
|
|
// Initial getTools should still work
|
|
expect(mockGetTools).toHaveBeenCalledTimes(1)
|
|
expect(mockListen).toHaveBeenCalled()
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
|
|
it('should only set up effect once with empty dependency array', async () => {
|
|
const { useTools } = await import('../useTools')
|
|
|
|
const { rerender } = renderHook(() => useTools())
|
|
|
|
// Initial render
|
|
expect(mockGetTools).toHaveBeenCalledTimes(1)
|
|
expect(mockListen).toHaveBeenCalledTimes(1)
|
|
|
|
// Rerender should not trigger additional calls
|
|
rerender()
|
|
expect(mockGetTools).toHaveBeenCalledTimes(1)
|
|
expect(mockListen).toHaveBeenCalledTimes(1)
|
|
})
|
|
}) |