test: fix failed tests
This commit is contained in:
parent
6e0218c084
commit
ca6f4f8977
@ -13,6 +13,38 @@ class TestAIEngine extends AIEngine {
|
||||
inference(data: any) {}
|
||||
|
||||
stopInference() {}
|
||||
|
||||
async list(): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
async load(modelId: string): Promise<any> {
|
||||
return { pid: 1, port: 8080, model_id: modelId, model_path: '', api_key: '' }
|
||||
}
|
||||
|
||||
async unload(sessionId: string): Promise<any> {
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
async chat(opts: any): Promise<any> {
|
||||
return { id: 'test', object: 'chat.completion', created: Date.now(), model: 'test', choices: [] }
|
||||
}
|
||||
|
||||
async delete(modelId: string): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
async import(modelId: string, opts: any): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
async abortImport(modelId: string): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
async getLoadedModels(): Promise<string[]> {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
describe('AIEngine', () => {
|
||||
@ -23,35 +55,31 @@ describe('AIEngine', () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should load model if provider matches', async () => {
|
||||
const model: any = { id: 'model1', engine: 'test-provider' } as any
|
||||
it('should load model successfully', async () => {
|
||||
const modelId = 'model1'
|
||||
|
||||
await engine.loadModel(model)
|
||||
const result = await engine.load(modelId)
|
||||
|
||||
expect(events.emit).toHaveBeenCalledWith(ModelEvent.OnModelReady, model)
|
||||
expect(result).toEqual({ pid: 1, port: 8080, model_id: modelId, model_path: '', api_key: '' })
|
||||
})
|
||||
|
||||
it('should not load model if provider does not match', async () => {
|
||||
const model: any = { id: 'model1', engine: 'other-provider' } as any
|
||||
it('should unload model successfully', async () => {
|
||||
const sessionId = 'session1'
|
||||
|
||||
await engine.loadModel(model)
|
||||
const result = await engine.unload(sessionId)
|
||||
|
||||
expect(events.emit).not.toHaveBeenCalledWith(ModelEvent.OnModelReady, model)
|
||||
expect(result).toEqual({ success: true })
|
||||
})
|
||||
|
||||
it('should unload model if provider matches', async () => {
|
||||
const model: Model = { id: 'model1', version: '1.0', engine: 'test-provider' } as any
|
||||
it('should list models', async () => {
|
||||
const result = await engine.list()
|
||||
|
||||
await engine.unloadModel(model)
|
||||
|
||||
expect(events.emit).toHaveBeenCalledWith(ModelEvent.OnModelStopped, model)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('should not unload model if provider does not match', async () => {
|
||||
const model: Model = { id: 'model1', version: '1.0', engine: 'other-provider' } as any
|
||||
it('should get loaded models', async () => {
|
||||
const result = await engine.getLoadedModels()
|
||||
|
||||
await engine.unloadModel(model)
|
||||
|
||||
expect(events.emit).not.toHaveBeenCalledWith(ModelEvent.OnModelStopped, model)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@ -28,9 +28,14 @@ export abstract class LocalOAIEngine extends OAIEngine {
|
||||
/**
|
||||
* Load the model.
|
||||
*/
|
||||
async loadModel(model: Model & { file_path?: string }): Promise<void> {}
|
||||
async loadModel(model: Model & { file_path?: string }): Promise<void> {
|
||||
// Implementation of loading the model
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the model.
|
||||
*/
|
||||
async unloadModel(model?: Model) {}
|
||||
async unloadModel(model?: Model) {
|
||||
// Implementation of unloading the model
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ConversationalExtension } from './index';
|
||||
import { InferenceExtension } from './index';
|
||||
import { AssistantExtension } from './index';
|
||||
import { ModelExtension } from './index';
|
||||
import * as Engines from './index';
|
||||
|
||||
describe('index.ts exports', () => {
|
||||
@ -17,9 +16,6 @@ describe('index.ts exports', () => {
|
||||
expect(AssistantExtension).toBeDefined();
|
||||
});
|
||||
|
||||
test('should export ModelExtension', () => {
|
||||
expect(ModelExtension).toBeDefined();
|
||||
});
|
||||
|
||||
test('should export Engines', () => {
|
||||
expect(Engines).toBeDefined();
|
||||
|
||||
@ -6,7 +6,6 @@ import * as message from './message';
|
||||
import * as inference from './inference';
|
||||
import * as file from './file';
|
||||
import * as config from './config';
|
||||
import * as huggingface from './huggingface';
|
||||
import * as miscellaneous from './miscellaneous';
|
||||
import * as api from './api';
|
||||
import * as setting from './setting';
|
||||
@ -19,7 +18,6 @@ import * as setting from './setting';
|
||||
expect(inference).toBeDefined();
|
||||
expect(file).toBeDefined();
|
||||
expect(config).toBeDefined();
|
||||
expect(huggingface).toBeDefined();
|
||||
expect(miscellaneous).toBeDefined();
|
||||
expect(api).toBeDefined();
|
||||
expect(setting).toBeDefined();
|
||||
|
||||
@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{ ignores: ['dist', 'coverage', '**/__tests__/**', '**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
|
||||
128
web-app/src/hooks/__tests__/useMediaQuery.test.ts
Normal file
128
web-app/src/hooks/__tests__/useMediaQuery.test.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useMediaQuery } from '../useMediaQuery'
|
||||
|
||||
// Mock window.matchMedia
|
||||
const mockMatchMedia = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: mockMatchMedia,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('useMediaQuery hook', () => {
|
||||
it('should return initial match value', () => {
|
||||
const mockMediaQueryList = {
|
||||
matches: true,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}
|
||||
|
||||
mockMatchMedia.mockReturnValue(mockMediaQueryList)
|
||||
|
||||
const { result } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||
|
||||
expect(result.current).toBe(true)
|
||||
expect(mockMatchMedia).toHaveBeenCalledWith('(min-width: 768px)')
|
||||
})
|
||||
|
||||
it('should return false when media query does not match', () => {
|
||||
const mockMediaQueryList = {
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}
|
||||
|
||||
mockMatchMedia.mockReturnValue(mockMediaQueryList)
|
||||
|
||||
const { result } = renderHook(() => useMediaQuery('(max-width: 767px)'))
|
||||
|
||||
expect(result.current).toBe(false)
|
||||
})
|
||||
|
||||
it('should update when media query changes', () => {
|
||||
const mockMediaQueryList = {
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}
|
||||
|
||||
mockMatchMedia.mockReturnValue(mockMediaQueryList)
|
||||
|
||||
const { result } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||
|
||||
expect(result.current).toBe(false)
|
||||
|
||||
// Simulate media query change
|
||||
const changeHandler = mockMediaQueryList.addEventListener.mock.calls[0][1]
|
||||
|
||||
act(() => {
|
||||
changeHandler({ matches: true })
|
||||
})
|
||||
|
||||
expect(result.current).toBe(true)
|
||||
})
|
||||
|
||||
it('should add event listener on mount', () => {
|
||||
const mockMediaQueryList = {
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}
|
||||
|
||||
mockMatchMedia.mockReturnValue(mockMediaQueryList)
|
||||
|
||||
renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||
|
||||
expect(mockMediaQueryList.addEventListener).toHaveBeenCalledWith('change', expect.any(Function))
|
||||
})
|
||||
|
||||
it('should remove event listener on unmount', () => {
|
||||
const mockMediaQueryList = {
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}
|
||||
|
||||
mockMatchMedia.mockReturnValue(mockMediaQueryList)
|
||||
|
||||
const { unmount } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||
|
||||
unmount()
|
||||
|
||||
expect(mockMediaQueryList.removeEventListener).toHaveBeenCalledWith('change', expect.any(Function))
|
||||
})
|
||||
|
||||
it('should handle different media queries', () => {
|
||||
const mockMediaQueryList = {
|
||||
matches: true,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}
|
||||
|
||||
mockMatchMedia.mockReturnValue(mockMediaQueryList)
|
||||
|
||||
const { result: result1 } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||
const { result: result2 } = renderHook(() => useMediaQuery('(max-width: 1024px)'))
|
||||
|
||||
expect(result1.current).toBe(true)
|
||||
expect(result2.current).toBe(true)
|
||||
expect(mockMatchMedia).toHaveBeenCalledWith('(min-width: 768px)')
|
||||
expect(mockMatchMedia).toHaveBeenCalledWith('(max-width: 1024px)')
|
||||
})
|
||||
|
||||
it('should handle matchMedia not being available', () => {
|
||||
// @ts-ignore
|
||||
delete window.matchMedia
|
||||
|
||||
const { result } = renderHook(() => useMediaQuery('(min-width: 768px)'))
|
||||
|
||||
expect(result.current).toBe(false)
|
||||
})
|
||||
})
|
||||
255
web-app/src/services/__tests__/models.test.ts
Normal file
255
web-app/src/services/__tests__/models.test.ts
Normal file
@ -0,0 +1,255 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import {
|
||||
fetchModels,
|
||||
fetchModelCatalog,
|
||||
updateModel,
|
||||
pullModel,
|
||||
abortDownload,
|
||||
deleteModel,
|
||||
getActiveModels,
|
||||
stopModel,
|
||||
stopAllModels,
|
||||
startModel,
|
||||
configurePullOptions,
|
||||
} from '../models'
|
||||
import { EngineManager } from '@janhq/core'
|
||||
|
||||
// Mock EngineManager
|
||||
vi.mock('@janhq/core', () => ({
|
||||
EngineManager: {
|
||||
instance: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn()
|
||||
|
||||
// Mock MODEL_CATALOG_URL
|
||||
Object.defineProperty(global, 'MODEL_CATALOG_URL', {
|
||||
value: 'https://example.com/models',
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
describe('models service', () => {
|
||||
const mockEngine = {
|
||||
list: vi.fn(),
|
||||
updateSettings: vi.fn(),
|
||||
import: vi.fn(),
|
||||
abortImport: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
getLoadedModels: vi.fn(),
|
||||
unload: vi.fn(),
|
||||
load: vi.fn(),
|
||||
}
|
||||
|
||||
const mockEngineManager = {
|
||||
get: vi.fn().mockReturnValue(mockEngine),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
;(EngineManager.instance as any).mockReturnValue(mockEngineManager)
|
||||
})
|
||||
|
||||
describe('fetchModels', () => {
|
||||
it('should fetch models successfully', async () => {
|
||||
const mockModels = [
|
||||
{ id: 'model1', name: 'Model 1' },
|
||||
{ id: 'model2', name: 'Model 2' },
|
||||
]
|
||||
mockEngine.list.mockResolvedValue(mockModels)
|
||||
|
||||
const result = await fetchModels()
|
||||
|
||||
expect(result).toEqual(mockModels)
|
||||
expect(mockEngine.list).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchModelCatalog', () => {
|
||||
it('should fetch model catalog successfully', async () => {
|
||||
const mockCatalog = [
|
||||
{
|
||||
model_name: 'GPT-4',
|
||||
description: 'Large language model',
|
||||
developer: 'OpenAI',
|
||||
downloads: 1000,
|
||||
num_quants: 5,
|
||||
quants: [],
|
||||
},
|
||||
]
|
||||
|
||||
;(fetch as any).mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue(mockCatalog),
|
||||
})
|
||||
|
||||
const result = await fetchModelCatalog()
|
||||
|
||||
expect(result).toEqual(mockCatalog)
|
||||
})
|
||||
|
||||
it('should handle fetch error', async () => {
|
||||
;(fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
})
|
||||
|
||||
await expect(fetchModelCatalog()).rejects.toThrow('Failed to fetch model catalog: 404 Not Found')
|
||||
})
|
||||
|
||||
it('should handle network error', async () => {
|
||||
;(fetch as any).mockRejectedValue(new Error('Network error'))
|
||||
|
||||
await expect(fetchModelCatalog()).rejects.toThrow('Failed to fetch model catalog: Network error')
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateModel', () => {
|
||||
it('should update model settings', async () => {
|
||||
const model = {
|
||||
id: 'model1',
|
||||
settings: [{ key: 'temperature', value: 0.7 }],
|
||||
}
|
||||
|
||||
await updateModel(model)
|
||||
|
||||
expect(mockEngine.updateSettings).toHaveBeenCalledWith(model.settings)
|
||||
})
|
||||
|
||||
it('should handle model without settings', async () => {
|
||||
const model = { id: 'model1' }
|
||||
|
||||
await updateModel(model)
|
||||
|
||||
expect(mockEngine.updateSettings).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('pullModel', () => {
|
||||
it('should pull model successfully', async () => {
|
||||
const id = 'model1'
|
||||
const modelPath = '/path/to/model'
|
||||
|
||||
await pullModel(id, modelPath)
|
||||
|
||||
expect(mockEngine.import).toHaveBeenCalledWith(id, { modelPath })
|
||||
})
|
||||
})
|
||||
|
||||
describe('abortDownload', () => {
|
||||
it('should abort download successfully', async () => {
|
||||
const id = 'model1'
|
||||
|
||||
await abortDownload(id)
|
||||
|
||||
expect(mockEngine.abortImport).toHaveBeenCalledWith(id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteModel', () => {
|
||||
it('should delete model successfully', async () => {
|
||||
const id = 'model1'
|
||||
|
||||
await deleteModel(id)
|
||||
|
||||
expect(mockEngine.delete).toHaveBeenCalledWith(id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getActiveModels', () => {
|
||||
it('should get active models successfully', async () => {
|
||||
const mockActiveModels = ['model1', 'model2']
|
||||
mockEngine.getLoadedModels.mockResolvedValue(mockActiveModels)
|
||||
|
||||
const result = await getActiveModels()
|
||||
|
||||
expect(result).toEqual(mockActiveModels)
|
||||
expect(mockEngine.getLoadedModels).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('stopModel', () => {
|
||||
it('should stop model successfully', async () => {
|
||||
const model = 'model1'
|
||||
const provider = 'openai'
|
||||
|
||||
await stopModel(model, provider)
|
||||
|
||||
expect(mockEngine.unload).toHaveBeenCalledWith(model)
|
||||
})
|
||||
})
|
||||
|
||||
describe('stopAllModels', () => {
|
||||
it('should stop all active models', async () => {
|
||||
const mockActiveModels = ['model1', 'model2']
|
||||
mockEngine.getLoadedModels.mockResolvedValue(mockActiveModels)
|
||||
|
||||
await stopAllModels()
|
||||
|
||||
expect(mockEngine.unload).toHaveBeenCalledTimes(2)
|
||||
expect(mockEngine.unload).toHaveBeenCalledWith('model1')
|
||||
expect(mockEngine.unload).toHaveBeenCalledWith('model2')
|
||||
})
|
||||
|
||||
it('should handle empty active models', async () => {
|
||||
mockEngine.getLoadedModels.mockResolvedValue(null)
|
||||
|
||||
await stopAllModels()
|
||||
|
||||
expect(mockEngine.unload).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('startModel', () => {
|
||||
it('should start model successfully', async () => {
|
||||
const provider = { provider: 'openai', models: [] } as ProviderObject
|
||||
const model = 'model1'
|
||||
const mockSession = { id: 'session1' }
|
||||
|
||||
mockEngine.load.mockResolvedValue(mockSession)
|
||||
|
||||
const result = await startModel(provider, model)
|
||||
|
||||
expect(result).toEqual(mockSession)
|
||||
expect(mockEngine.load).toHaveBeenCalledWith(model)
|
||||
})
|
||||
|
||||
it('should handle start model error', async () => {
|
||||
const provider = { provider: 'openai', models: [] } as ProviderObject
|
||||
const model = 'model1'
|
||||
const error = new Error('Failed to start model')
|
||||
|
||||
mockEngine.load.mockRejectedValue(error)
|
||||
|
||||
await expect(startModel(provider, model)).rejects.toThrow(error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('configurePullOptions', () => {
|
||||
it('should configure proxy options', async () => {
|
||||
const proxyOptions = {
|
||||
proxyEnabled: true,
|
||||
proxyUrl: 'http://proxy.com',
|
||||
proxyUsername: 'user',
|
||||
proxyPassword: 'pass',
|
||||
proxyIgnoreSSL: false,
|
||||
verifyProxySSL: true,
|
||||
verifyProxyHostSSL: true,
|
||||
verifyPeerSSL: true,
|
||||
verifyHostSSL: true,
|
||||
noProxy: '',
|
||||
}
|
||||
|
||||
// Mock console.log to avoid output during tests
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await configurePullOptions(proxyOptions)
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Configuring proxy options:', proxyOptions)
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
})
|
||||
176
web-app/src/services/__tests__/threads.test.ts
Normal file
176
web-app/src/services/__tests__/threads.test.ts
Normal file
@ -0,0 +1,176 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { fetchThreads, createThread, updateThread, deleteThread } from '../threads'
|
||||
import { ExtensionManager } from '@/lib/extension'
|
||||
import { ConversationalExtension, ExtensionTypeEnum } from '@janhq/core'
|
||||
import { defaultAssistant } from '@/hooks/useAssistant'
|
||||
|
||||
// Mock ExtensionManager
|
||||
vi.mock('@/lib/extension', () => ({
|
||||
ExtensionManager: {
|
||||
getInstance: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/useAssistant', () => ({
|
||||
defaultAssistant: {
|
||||
id: 'jan',
|
||||
name: 'Jan',
|
||||
instructions: 'You are a helpful assistant.',
|
||||
},
|
||||
}))
|
||||
|
||||
describe('threads service', () => {
|
||||
const mockConversationalExtension = {
|
||||
listThreads: vi.fn(),
|
||||
createThread: vi.fn(),
|
||||
modifyThread: vi.fn(),
|
||||
deleteThread: vi.fn(),
|
||||
}
|
||||
|
||||
const mockExtensionManager = {
|
||||
get: vi.fn().mockReturnValue(mockConversationalExtension),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
;(ExtensionManager.getInstance as any).mockReturnValue(mockExtensionManager)
|
||||
})
|
||||
|
||||
describe('fetchThreads', () => {
|
||||
it('should fetch and transform threads successfully', async () => {
|
||||
const mockThreads = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Test Thread',
|
||||
updated: 1234567890,
|
||||
metadata: { order: 1, is_favorite: true },
|
||||
assistants: [{ model: { id: 'gpt-4', engine: 'openai' } }],
|
||||
},
|
||||
]
|
||||
|
||||
mockConversationalExtension.listThreads.mockResolvedValue(mockThreads)
|
||||
|
||||
const result = await fetchThreads()
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toMatchObject({
|
||||
id: '1',
|
||||
title: 'Test Thread',
|
||||
updated: 1234567890,
|
||||
order: 1,
|
||||
isFavorite: true,
|
||||
model: { id: 'gpt-4', provider: 'openai' },
|
||||
assistants: [{ model: { id: 'gpt-4', engine: 'openai' } }],
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle empty threads array', async () => {
|
||||
mockConversationalExtension.listThreads.mockResolvedValue([])
|
||||
|
||||
const result = await fetchThreads()
|
||||
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle error and return empty array', async () => {
|
||||
mockConversationalExtension.listThreads.mockRejectedValue(new Error('API Error'))
|
||||
|
||||
const result = await fetchThreads()
|
||||
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle null/undefined response', async () => {
|
||||
mockConversationalExtension.listThreads.mockResolvedValue(null)
|
||||
|
||||
const result = await fetchThreads()
|
||||
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('createThread', () => {
|
||||
it('should create thread successfully', async () => {
|
||||
const inputThread = {
|
||||
id: '1',
|
||||
title: 'New Thread',
|
||||
model: { id: 'gpt-4', provider: 'openai' },
|
||||
assistants: [defaultAssistant],
|
||||
order: 1,
|
||||
}
|
||||
|
||||
const mockCreatedThread = {
|
||||
id: '1',
|
||||
title: 'New Thread',
|
||||
updated: 1234567890,
|
||||
assistants: [{ model: { id: 'gpt-4', engine: 'openai' } }],
|
||||
metadata: { order: 1 },
|
||||
}
|
||||
|
||||
mockConversationalExtension.createThread.mockResolvedValue(mockCreatedThread)
|
||||
|
||||
const result = await createThread(inputThread as Thread)
|
||||
|
||||
expect(result).toMatchObject({
|
||||
id: '1',
|
||||
title: 'New Thread',
|
||||
updated: 1234567890,
|
||||
model: { id: 'gpt-4', provider: 'openai' },
|
||||
order: 1,
|
||||
assistants: [{ model: { id: 'gpt-4', engine: 'openai' } }],
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle creation error and return original thread', async () => {
|
||||
const inputThread = {
|
||||
id: '1',
|
||||
title: 'New Thread',
|
||||
model: { id: 'gpt-4', provider: 'openai' },
|
||||
}
|
||||
|
||||
mockConversationalExtension.createThread.mockRejectedValue(new Error('Creation failed'))
|
||||
|
||||
const result = await createThread(inputThread as Thread)
|
||||
|
||||
expect(result).toEqual(inputThread)
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateThread', () => {
|
||||
it('should update thread successfully', async () => {
|
||||
const thread = {
|
||||
id: '1',
|
||||
title: 'Updated Thread',
|
||||
model: { id: 'gpt-4', provider: 'openai' },
|
||||
assistants: [defaultAssistant],
|
||||
isFavorite: true,
|
||||
order: 2,
|
||||
}
|
||||
|
||||
const result = updateThread(thread as Thread)
|
||||
|
||||
expect(mockConversationalExtension.modifyThread).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: '1',
|
||||
title: 'Updated Thread',
|
||||
assistants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
model: { id: 'gpt-4', engine: 'openai' },
|
||||
}),
|
||||
]),
|
||||
metadata: { is_favorite: true, order: 2 },
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteThread', () => {
|
||||
it('should delete thread successfully', () => {
|
||||
const threadId = '1'
|
||||
|
||||
deleteThread(threadId)
|
||||
|
||||
expect(mockConversationalExtension.deleteThread).toHaveBeenCalledWith(threadId)
|
||||
})
|
||||
})
|
||||
})
|
||||
14
web-app/src/utils/__tests__/error.test.ts
Normal file
14
web-app/src/utils/__tests__/error.test.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { OUT_OF_CONTEXT_SIZE } from '../error'
|
||||
|
||||
describe('error utilities', () => {
|
||||
describe('OUT_OF_CONTEXT_SIZE', () => {
|
||||
it('should have correct error message', () => {
|
||||
expect(OUT_OF_CONTEXT_SIZE).toBe('the request exceeds the available context size.')
|
||||
})
|
||||
|
||||
it('should be a string', () => {
|
||||
expect(typeof OUT_OF_CONTEXT_SIZE).toBe('string')
|
||||
})
|
||||
})
|
||||
})
|
||||
71
web-app/src/utils/__tests__/highlight.test.ts
Normal file
71
web-app/src/utils/__tests__/highlight.test.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { highlightFzfMatch } from '../highlight'
|
||||
|
||||
describe('highlight utility', () => {
|
||||
describe('highlightFzfMatch', () => {
|
||||
it('should highlight characters at specified positions', () => {
|
||||
const text = 'Hello World'
|
||||
const positions = [0, 6]
|
||||
const result = highlightFzfMatch(text, positions)
|
||||
|
||||
expect(result).toBe('<span class="search-highlight">H</span>ello <span class="search-highlight">W</span>orld')
|
||||
})
|
||||
|
||||
it('should handle empty positions array', () => {
|
||||
const text = 'Hello World'
|
||||
const positions: number[] = []
|
||||
const result = highlightFzfMatch(text, positions)
|
||||
|
||||
expect(result).toBe('Hello World')
|
||||
})
|
||||
|
||||
it('should handle empty text', () => {
|
||||
const text = ''
|
||||
const positions = [0, 1]
|
||||
const result = highlightFzfMatch(text, positions)
|
||||
|
||||
expect(result).toBe('')
|
||||
})
|
||||
|
||||
it('should handle positions out of bounds', () => {
|
||||
const text = 'Hello'
|
||||
const positions = [0, 10]
|
||||
const result = highlightFzfMatch(text, positions)
|
||||
|
||||
expect(result).toBe('<span class="search-highlight">H</span>ello')
|
||||
})
|
||||
|
||||
it('should handle custom highlight class', () => {
|
||||
const text = 'Hello World'
|
||||
const positions = [0]
|
||||
const result = highlightFzfMatch(text, positions, 'custom-highlight')
|
||||
|
||||
expect(result).toBe('<span class="custom-highlight">H</span>ello World')
|
||||
})
|
||||
|
||||
it('should sort positions automatically', () => {
|
||||
const text = 'Hello World'
|
||||
const positions = [6, 0]
|
||||
const result = highlightFzfMatch(text, positions)
|
||||
|
||||
expect(result).toBe('<span class="search-highlight">H</span>ello <span class="search-highlight">W</span>orld')
|
||||
})
|
||||
|
||||
it('should handle multiple consecutive positions', () => {
|
||||
const text = 'Hello'
|
||||
const positions = [0, 1, 2]
|
||||
const result = highlightFzfMatch(text, positions)
|
||||
|
||||
expect(result).toBe('<span class="search-highlight">H</span><span class="search-highlight">e</span><span class="search-highlight">l</span>lo')
|
||||
})
|
||||
|
||||
it('should handle null or undefined positions', () => {
|
||||
const text = 'Hello World'
|
||||
const result1 = highlightFzfMatch(text, null as any)
|
||||
const result2 = highlightFzfMatch(text, undefined as any)
|
||||
|
||||
expect(result1).toBe('Hello World')
|
||||
expect(result2).toBe('Hello World')
|
||||
})
|
||||
})
|
||||
})
|
||||
42
web-app/src/utils/__tests__/teamEmoji.test.ts
Normal file
42
web-app/src/utils/__tests__/teamEmoji.test.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { teamEmoji } from '../teamEmoji'
|
||||
|
||||
describe('teamEmoji utility', () => {
|
||||
describe('teamEmoji', () => {
|
||||
it('should contain team member data', () => {
|
||||
expect(teamEmoji).toBeInstanceOf(Array)
|
||||
expect(teamEmoji.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should have correct structure for team members', () => {
|
||||
const member = teamEmoji[0]
|
||||
expect(member).toHaveProperty('names')
|
||||
expect(member).toHaveProperty('imgUrl')
|
||||
expect(member).toHaveProperty('id')
|
||||
expect(Array.isArray(member.names)).toBe(true)
|
||||
expect(typeof member.imgUrl).toBe('string')
|
||||
expect(typeof member.id).toBe('string')
|
||||
})
|
||||
|
||||
it('should contain expected team members', () => {
|
||||
const memberIds = teamEmoji.map(m => m.id)
|
||||
expect(memberIds).toContain('louis')
|
||||
expect(memberIds).toContain('emre')
|
||||
expect(memberIds).toContain('alex')
|
||||
expect(memberIds).toContain('daniel')
|
||||
expect(memberIds).toContain('bach')
|
||||
})
|
||||
|
||||
it('should have unique IDs', () => {
|
||||
const ids = teamEmoji.map(m => m.id)
|
||||
const uniqueIds = [...new Set(ids)]
|
||||
expect(ids.length).toBe(uniqueIds.length)
|
||||
})
|
||||
|
||||
it('should have valid image URLs', () => {
|
||||
teamEmoji.forEach(member => {
|
||||
expect(member.imgUrl).toMatch(/^\/images\/emoji\/.*\.png$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user