diff --git a/.github/workflows/jan-linter-and-test.yml b/.github/workflows/jan-linter-and-test.yml index e09c23f04..2aa871fb7 100644 --- a/.github/workflows/jan-linter-and-test.yml +++ b/.github/workflows/jan-linter-and-test.yml @@ -68,7 +68,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ref-lcov.info - path: coverage/merged/lcov.info + path: coverage/lcov.info test-on-macos: runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'macos-latest' || 'macos-selfhosted-12-arm64' }} @@ -263,7 +263,7 @@ jobs: uses: barecheck/code-coverage-action@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} - lcov-file: './coverage/merged/lcov.info' + lcov-file: './coverage/lcov.info' base-lcov-file: './lcov.info' send-summary-comment: true show-annotations: 'warning' diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml index a9e1f89bd..ede74fa17 100644 --- a/.github/workflows/template-tauri-build-linux-x64.yml +++ b/.github/workflows/template-tauri-build-linux-x64.yml @@ -108,7 +108,6 @@ jobs: mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json if [ "${{ inputs.channel }}" != "stable" ]; then jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", - "usr/lib/Jan-${{ inputs.channel }}/binaries": "binaries/deps", "usr/lib/Jan-${{ inputs.channel }}/resources/lib/libvulkan.so": "resources/lib/libvulkan.so"}' ./src-tauri/tauri.linux.conf.json > /tmp/tauri.linux.conf.json mv /tmp/tauri.linux.conf.json ./src-tauri/tauri.linux.conf.json fi diff --git a/core/jest.config.js b/core/jest.config.js deleted file mode 100644 index f5fd6bb80..000000000 --- a/core/jest.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - collectCoverageFrom: ['src/**/*.{ts,tsx}'], - moduleNameMapper: { - '@/(.*)': '/src/$1', - }, - runner: './testRunner.js', - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - diagnostics: false, - }, - ], - }, -} diff --git a/core/package.json b/core/package.json index 53036cd12..eec56a733 100644 --- a/core/package.json +++ b/core/package.json @@ -17,27 +17,28 @@ "author": "Jan ", "scripts": { "lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'", - "test": "jest", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "prebuild": "rimraf dist", "build": "tsc -p . && rolldown -c rolldown.config.mjs" }, "devDependencies": { "@npmcli/arborist": "^7.1.0", - "@types/jest": "^30.0.0", "@types/node": "^22.10.0", + "@vitest/coverage-v8": "^2.1.8", + "@vitest/ui": "^2.1.8", "eslint": "8.57.0", - "eslint-plugin-jest": "^27.9.0", - "jest": "^30.0.3", - "jest-junit": "^16.0.0", - "jest-runner": "^30.0.3", + "happy-dom": "^15.11.6", "pacote": "^21.0.0", "request": "^2.88.2", "request-progress": "^3.0.0", "rimraf": "^6.0.1", "rolldown": "1.0.0-beta.1", - "ts-jest": "^29.2.5", "tslib": "^2.6.2", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^2.1.8" }, "dependencies": { "rxjs": "^7.8.1", diff --git a/core/src/browser/core.test.ts b/core/src/browser/core.test.ts index 6197da023..67c91c2a7 100644 --- a/core/src/browser/core.test.ts +++ b/core/src/browser/core.test.ts @@ -1,6 +1,4 @@ -/** - * @jest-environment jsdom - */ +import { describe, it, expect, vi } from 'vitest' import { openExternalUrl } from './core' import { joinPath } from './core' import { openFileExplorer } from './core' @@ -12,7 +10,7 @@ describe('test core apis', () => { const url = 'http://example.com' globalThis.core = { api: { - openExternalUrl: jest.fn().mockResolvedValue('opened'), + openExternalUrl: vi.fn().mockResolvedValue('opened'), }, } const result = await openExternalUrl(url) @@ -24,7 +22,7 @@ describe('test core apis', () => { const paths = ['/path/one', '/path/two'] globalThis.core = { api: { - joinPath: jest.fn().mockResolvedValue('/path/one/path/two'), + joinPath: vi.fn().mockResolvedValue('/path/one/path/two'), }, } const result = await joinPath(paths) @@ -36,7 +34,7 @@ describe('test core apis', () => { const path = '/path/to/open' globalThis.core = { api: { - openFileExplorer: jest.fn().mockResolvedValue('opened'), + openFileExplorer: vi.fn().mockResolvedValue('opened'), }, } const result = await openFileExplorer(path) @@ -47,7 +45,7 @@ describe('test core apis', () => { it('should get jan data folder path', async () => { globalThis.core = { api: { - getJanDataFolderPath: jest.fn().mockResolvedValue('/path/to/jan/data'), + getJanDataFolderPath: vi.fn().mockResolvedValue('/path/to/jan/data'), }, } const result = await getJanDataFolderPath() @@ -58,7 +56,7 @@ describe('test core apis', () => { describe('dirName - just a pass thru api', () => { it('should retrieve the directory name from a file path', async () => { - const mockDirName = jest.fn() + const mockDirName = vi.fn() globalThis.core = { api: { dirName: mockDirName.mockResolvedValue('/path/to'), diff --git a/core/src/browser/events.test.ts b/core/src/browser/events.test.ts index 23b4d78d9..5c0c7c3af 100644 --- a/core/src/browser/events.test.ts +++ b/core/src/browser/events.test.ts @@ -1,11 +1,11 @@ +import { it, expect, vi } from 'vitest' import { events } from './events'; -import { jest } from '@jest/globals'; it('should emit an event', () => { const mockObject = { key: 'value' }; globalThis.core = { events: { - emit: jest.fn() + emit: vi.fn() } }; events.emit('testEvent', mockObject); @@ -14,10 +14,10 @@ it('should emit an event', () => { it('should remove an observer for an event', () => { - const mockHandler = jest.fn(); + const mockHandler = vi.fn(); globalThis.core = { events: { - off: jest.fn() + off: vi.fn() } }; events.off('testEvent', mockHandler); @@ -26,10 +26,10 @@ it('should remove an observer for an event', () => { it('should add an observer for an event', () => { - const mockHandler = jest.fn(); + const mockHandler = vi.fn(); globalThis.core = { events: { - on: jest.fn() + on: vi.fn() } }; events.on('testEvent', mockHandler); diff --git a/core/src/browser/extension.test.ts b/core/src/browser/extension.test.ts index b2a1d1e73..2f7f9c14d 100644 --- a/core/src/browser/extension.test.ts +++ b/core/src/browser/extension.test.ts @@ -1,7 +1,8 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { BaseExtension } from './extension' import { SettingComponentProps } from '../types' -jest.mock('./core') -jest.mock('./fs') +vi.mock('./core') +vi.mock('./fs') class TestBaseExtension extends BaseExtension { onLoad(): void {} @@ -16,7 +17,7 @@ describe('BaseExtension', () => { }) afterEach(() => { - jest.resetAllMocks() + vi.clearAllMocks() }) it('should have the correct properties', () => { @@ -56,7 +57,7 @@ describe('BaseExtension', () => { }) afterEach(() => { - jest.resetAllMocks() + vi.clearAllMocks() }) it('should have the correct properties', () => { @@ -108,7 +109,7 @@ describe('BaseExtension', () => { Object.defineProperty(global, 'localStorage', { value: localStorageMock, }) - const mock = jest.spyOn(localStorage, 'setItem') + const mock = vi.spyOn(localStorage, 'setItem') await baseExtension.registerSettings(settings) expect(mock).toHaveBeenCalledWith( @@ -122,7 +123,7 @@ describe('BaseExtension', () => { { key: 'setting1', controllerProps: { value: 'value1' } } as any, ] - jest.spyOn(baseExtension, 'getSettings').mockResolvedValue(settings) + vi.spyOn(baseExtension, 'getSettings').mockResolvedValue(settings) const value = await baseExtension.getSetting('setting1', 'defaultValue') expect(value).toBe('value1') @@ -136,8 +137,8 @@ describe('BaseExtension', () => { { key: 'setting1', controllerProps: { value: 'value1' } } as any, ] - jest.spyOn(baseExtension, 'getSettings').mockResolvedValue(settings) - const mockSetItem = jest.spyOn(localStorage, 'setItem') + vi.spyOn(baseExtension, 'getSettings').mockResolvedValue(settings) + const mockSetItem = vi.spyOn(localStorage, 'setItem') await baseExtension.updateSettings([ { key: 'setting1', controllerProps: { value: 'newValue' } } as any, diff --git a/core/src/browser/extensions/assistant.test.ts b/core/src/browser/extensions/assistant.test.ts index ae81b0985..87dcd4829 100644 --- a/core/src/browser/extensions/assistant.test.ts +++ b/core/src/browser/extensions/assistant.test.ts @@ -1,4 +1,5 @@ +import { it, expect } from 'vitest' import { AssistantExtension } from './assistant'; import { ExtensionTypeEnum } from '../extension'; diff --git a/core/src/browser/extensions/conversational.test.ts b/core/src/browser/extensions/conversational.test.ts index 8046383c9..c08468905 100644 --- a/core/src/browser/extensions/conversational.test.ts +++ b/core/src/browser/extensions/conversational.test.ts @@ -1,3 +1,4 @@ +import { describe, it, test, expect, beforeEach } from 'vitest' import { ConversationalExtension } from './conversational' import { ExtensionTypeEnum } from '../extension' import { Thread, ThreadAssistantInfo, ThreadMessage } from '../../types' diff --git a/core/src/browser/extensions/engines/AIEngine.test.ts b/core/src/browser/extensions/engines/AIEngine.test.ts index 2ead360a8..192143376 100644 --- a/core/src/browser/extensions/engines/AIEngine.test.ts +++ b/core/src/browser/extensions/engines/AIEngine.test.ts @@ -1,10 +1,11 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' import { AIEngine } from './AIEngine' import { events } from '../../events' import { ModelEvent, Model } from '../../../types' -jest.mock('../../events') -jest.mock('./EngineManager') -jest.mock('../../fs') +vi.mock('../../events') +vi.mock('./EngineManager') +vi.mock('../../fs') class TestAIEngine extends AIEngine { onUnload(): void {} @@ -52,7 +53,7 @@ describe('AIEngine', () => { beforeEach(() => { engine = new TestAIEngine('', '') - jest.clearAllMocks() + vi.clearAllMocks() }) it('should load model successfully', async () => { diff --git a/core/src/browser/extensions/engines/EngineManager.test.ts b/core/src/browser/extensions/engines/EngineManager.test.ts index 49cf54b98..8f40449fc 100644 --- a/core/src/browser/extensions/engines/EngineManager.test.ts +++ b/core/src/browser/extensions/engines/EngineManager.test.ts @@ -1,6 +1,4 @@ -/** - * @jest-environment jsdom - */ +import { describe, it, test, expect, beforeEach } from 'vitest' import { EngineManager } from './EngineManager' import { AIEngine } from './AIEngine' import { InferenceEngine } from '../../../types' diff --git a/core/src/browser/extensions/engines/LocalOAIEngine.test.ts b/core/src/browser/extensions/engines/LocalOAIEngine.test.ts index dd41ad787..5f2563d56 100644 --- a/core/src/browser/extensions/engines/LocalOAIEngine.test.ts +++ b/core/src/browser/extensions/engines/LocalOAIEngine.test.ts @@ -1,11 +1,9 @@ -/** - * @jest-environment jsdom - */ +import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest' import { LocalOAIEngine } from './LocalOAIEngine' import { events } from '../../events' import { Model, ModelEvent } from '../../../types' -jest.mock('../../events') +vi.mock('../../events') class TestLocalOAIEngine extends LocalOAIEngine { inferenceUrl = 'http://test-local-inference-url' @@ -43,12 +41,12 @@ describe('LocalOAIEngine', () => { beforeEach(() => { engine = new TestLocalOAIEngine('', '') - jest.clearAllMocks() + vi.clearAllMocks() }) describe('onLoad', () => { it('should call super.onLoad and subscribe to model events', () => { - const superOnLoadSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(engine)), 'onLoad') + const superOnLoadSpy = vi.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(engine)), 'onLoad') engine.onLoad() @@ -64,11 +62,11 @@ describe('LocalOAIEngine', () => { }) it('should load model when OnModelInit event is triggered', () => { - const loadModelSpy = jest.spyOn(engine, 'loadModel') + const loadModelSpy = vi.spyOn(engine, 'loadModel') engine.onLoad() // Get the event handler for OnModelInit - const onModelInitCall = (events.on as jest.Mock).mock.calls.find( + const onModelInitCall = (events.on as Mock).mock.calls.find( call => call[0] === ModelEvent.OnModelInit ) const onModelInitHandler = onModelInitCall[1] @@ -80,11 +78,11 @@ describe('LocalOAIEngine', () => { }) it('should unload model when OnModelStop event is triggered', () => { - const unloadModelSpy = jest.spyOn(engine, 'unloadModel') + const unloadModelSpy = vi.spyOn(engine, 'unloadModel') engine.onLoad() // Get the event handler for OnModelStop - const onModelStopCall = (events.on as jest.Mock).mock.calls.find( + const onModelStopCall = (events.on as Mock).mock.calls.find( call => call[0] === ModelEvent.OnModelStop ) const onModelStopHandler = onModelStopCall[1] diff --git a/core/src/browser/extensions/engines/OAIEngine.test.ts b/core/src/browser/extensions/engines/OAIEngine.test.ts index 0e985fd1b..5d626b006 100644 --- a/core/src/browser/extensions/engines/OAIEngine.test.ts +++ b/core/src/browser/extensions/engines/OAIEngine.test.ts @@ -1,6 +1,4 @@ -/** - * @jest-environment jsdom - */ +import { describe, it, expect, beforeEach, vi } from 'vitest' import { OAIEngine } from './OAIEngine' import { events } from '../../events' import { @@ -13,7 +11,7 @@ import { ContentType, } from '../../../types' -jest.mock('../../events') +vi.mock('../../events') class TestOAIEngine extends OAIEngine { inferenceUrl = 'http://test-inference-url' @@ -29,7 +27,7 @@ describe('OAIEngine', () => { beforeEach(() => { engine = new TestOAIEngine('', '') - jest.clearAllMocks() + vi.clearAllMocks() }) it('should subscribe to events on load', () => { diff --git a/core/src/browser/extensions/engines/RemoteOAIEngine.test.ts b/core/src/browser/extensions/engines/RemoteOAIEngine.test.ts index 871499f45..b3e544139 100644 --- a/core/src/browser/extensions/engines/RemoteOAIEngine.test.ts +++ b/core/src/browser/extensions/engines/RemoteOAIEngine.test.ts @@ -1,6 +1,4 @@ -/** - * @jest-environment jsdom - */ +import { describe, test, expect, beforeEach, vi } from 'vitest' import { RemoteOAIEngine } from './' class TestRemoteOAIEngine extends RemoteOAIEngine { @@ -16,8 +14,8 @@ describe('RemoteOAIEngine', () => { }) test('should call onLoad and super.onLoad', () => { - const onLoadSpy = jest.spyOn(engine, 'onLoad') - const superOnLoadSpy = jest.spyOn(Object.getPrototypeOf(RemoteOAIEngine.prototype), 'onLoad') + const onLoadSpy = vi.spyOn(engine, 'onLoad') + const superOnLoadSpy = vi.spyOn(Object.getPrototypeOf(RemoteOAIEngine.prototype), 'onLoad') engine.onLoad() expect(onLoadSpy).toHaveBeenCalled() diff --git a/core/src/browser/extensions/engines/index.test.ts b/core/src/browser/extensions/engines/index.test.ts index 4c0ef11d8..e77fcc14e 100644 --- a/core/src/browser/extensions/engines/index.test.ts +++ b/core/src/browser/extensions/engines/index.test.ts @@ -1,6 +1,6 @@ - -import { expect } from '@jest/globals'; +import { it, expect } from 'vitest' +import * as engines from './index' it('should re-export all exports from ./AIEngine', () => { - expect(require('./index')).toHaveProperty('AIEngine'); -}); + expect(engines).toHaveProperty('AIEngine') +}) diff --git a/core/src/browser/extensions/index.test.ts b/core/src/browser/extensions/index.test.ts index feb72db5e..2b1adad4a 100644 --- a/core/src/browser/extensions/index.test.ts +++ b/core/src/browser/extensions/index.test.ts @@ -1,3 +1,4 @@ +import { describe, test, expect } from 'vitest' import { ConversationalExtension } from './index'; import { InferenceExtension } from './index'; import { AssistantExtension } from './index'; diff --git a/core/src/browser/extensions/inference.test.ts b/core/src/browser/extensions/inference.test.ts index 45ec9d172..09ff802ba 100644 --- a/core/src/browser/extensions/inference.test.ts +++ b/core/src/browser/extensions/inference.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect, beforeEach } from 'vitest' import { MessageRequest, ThreadMessage } from '../../types' import { BaseExtension, ExtensionTypeEnum } from '../extension' import { InferenceExtension } from './' diff --git a/core/src/browser/fs.test.ts b/core/src/browser/fs.test.ts index 3f83d0856..136d0145d 100644 --- a/core/src/browser/fs.test.ts +++ b/core/src/browser/fs.test.ts @@ -1,21 +1,22 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' import { fs } from './fs' describe('fs module', () => { beforeEach(() => { globalThis.core = { api: { - writeFileSync: jest.fn(), - writeBlob: jest.fn(), - readFileSync: jest.fn(), - existsSync: jest.fn(), - readdirSync: jest.fn(), - mkdir: jest.fn(), - rm: jest.fn(), - unlinkSync: jest.fn(), - appendFileSync: jest.fn(), - copyFile: jest.fn(), - getGgufFiles: jest.fn(), - fileStat: jest.fn(), + writeFileSync: vi.fn(), + writeBlob: vi.fn(), + readFileSync: vi.fn(), + existsSync: vi.fn(), + readdirSync: vi.fn(), + mkdir: vi.fn(), + rm: vi.fn(), + unlinkSync: vi.fn(), + appendFileSync: vi.fn(), + copyFile: vi.fn(), + getGgufFiles: vi.fn(), + fileStat: vi.fn(), }, } }) diff --git a/core/src/browser/index.test.ts b/core/src/browser/index.test.ts index fcdb635ff..a02604c20 100644 --- a/core/src/browser/index.test.ts +++ b/core/src/browser/index.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest' import * as Core from './core' import * as Events from './events' import * as FileSystem from './fs' diff --git a/core/src/browser/models/manager.test.ts b/core/src/browser/models/manager.test.ts index 189ca1209..90626b22e 100644 --- a/core/src/browser/models/manager.test.ts +++ b/core/src/browser/models/manager.test.ts @@ -1,10 +1,11 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' import { ModelManager } from './manager' import { Model, ModelEvent } from '../../types' import { events } from '../events' -jest.mock('../events', () => ({ +vi.mock('../events', () => ({ events: { - emit: jest.fn(), + emit: vi.fn(), }, })) @@ -20,7 +21,7 @@ describe('ModelManager', () => { let mockModel: Model beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() ;(global.window as any).core = {} modelManager = new ModelManager() mockModel = { diff --git a/core/src/browser/models/utils.test.ts b/core/src/browser/models/utils.test.ts index 2a1a09d23..5600a28be 100644 --- a/core/src/browser/models/utils.test.ts +++ b/core/src/browser/models/utils.test.ts @@ -1,4 +1,5 @@ // web/utils/modelParam.test.ts +import { describe, it, expect } from 'vitest' import { normalizeValue, validationRules, diff --git a/core/src/index.test.ts b/core/src/index.test.ts index a1bd7c6b9..f41cf5736 100644 --- a/core/src/index.test.ts +++ b/core/src/index.test.ts @@ -1,4 +1,5 @@ +import { it, expect } from 'vitest' it('should declare global object core when importing the module and then deleting it', () => { import('./index'); diff --git a/core/src/test/setup.ts b/core/src/test/setup.ts new file mode 100644 index 000000000..c597a3748 --- /dev/null +++ b/core/src/test/setup.ts @@ -0,0 +1,19 @@ +import { vi } from 'vitest' + +// Ensure window exists in test environment +if (typeof window === 'undefined') { + global.window = {} as any +} + +// Mock window.core for browser tests +if (!window.core) { + Object.defineProperty(window, 'core', { + value: { + engineManager: undefined + }, + writable: true, + configurable: true + }) +} + +// Add any other global mocks needed for core tests \ No newline at end of file diff --git a/core/src/types/api/index.test.ts b/core/src/types/api/index.test.ts index 6f2f2dcdb..c8aaf0002 100644 --- a/core/src/types/api/index.test.ts +++ b/core/src/types/api/index.test.ts @@ -1,5 +1,6 @@ +import { test, expect } from 'vitest' import { NativeRoute } from '../index'; test('testNativeRouteEnum', () => { diff --git a/core/src/types/assistant/assistantEvent.test.ts b/core/src/types/assistant/assistantEvent.test.ts index 4b1ed552c..2d985c7f4 100644 --- a/core/src/types/assistant/assistantEvent.test.ts +++ b/core/src/types/assistant/assistantEvent.test.ts @@ -1,4 +1,6 @@ +import { it, expect } from 'vitest' import { AssistantEvent } from './assistantEvent'; + it('dummy test', () => { expect(true).toBe(true); }); it('should contain OnAssistantsUpdate event', () => { diff --git a/core/src/types/config/appConfigEvent.test.ts b/core/src/types/config/appConfigEvent.test.ts index 6000156c7..a51dcf3a1 100644 --- a/core/src/types/config/appConfigEvent.test.ts +++ b/core/src/types/config/appConfigEvent.test.ts @@ -1,8 +1,9 @@ - import { AppConfigurationEventName } from './appConfigEvent'; - - describe('AppConfigurationEventName', () => { +import { describe, it, expect } from 'vitest' +import { AppConfigurationEventName } from './appConfigEvent'; + +describe('AppConfigurationEventName', () => { it('should have the correct value for OnConfigurationUpdate', () => { expect(AppConfigurationEventName.OnConfigurationUpdate).toBe('OnConfigurationUpdate'); }); diff --git a/core/src/types/index.test.ts b/core/src/types/index.test.ts index c58c6b2e1..a71288ec9 100644 --- a/core/src/types/index.test.ts +++ b/core/src/types/index.test.ts @@ -1,4 +1,5 @@ +import { test, expect } from 'vitest' import * as assistant from './assistant'; import * as model from './model'; import * as thread from './thread'; @@ -10,7 +11,7 @@ import * as miscellaneous from './miscellaneous'; import * as api from './api'; import * as setting from './setting'; - test('test_module_exports', () => { +test('test_module_exports', () => { expect(assistant).toBeDefined(); expect(model).toBeDefined(); expect(thread).toBeDefined(); diff --git a/core/src/types/inference/inferenceEntity.test.ts b/core/src/types/inference/inferenceEntity.test.ts index a2c06e32b..70974161b 100644 --- a/core/src/types/inference/inferenceEntity.test.ts +++ b/core/src/types/inference/inferenceEntity.test.ts @@ -1,8 +1,9 @@ - import { ChatCompletionMessage, ChatCompletionRole } from './inferenceEntity'; - - test('test_chatCompletionMessage_withStringContent_andSystemRole', () => { +import { test, expect } from 'vitest' +import { ChatCompletionMessage, ChatCompletionRole } from './inferenceEntity'; + +test('test_chatCompletionMessage_withStringContent_andSystemRole', () => { const message: ChatCompletionMessage = { content: 'Hello, world!', role: ChatCompletionRole.System, diff --git a/core/src/types/inference/inferenceEvent.test.ts b/core/src/types/inference/inferenceEvent.test.ts index 1cb44fdbb..b64628708 100644 --- a/core/src/types/inference/inferenceEvent.test.ts +++ b/core/src/types/inference/inferenceEvent.test.ts @@ -1,7 +1,8 @@ - import { InferenceEvent } from './inferenceEvent'; - - test('testInferenceEventEnumContainsOnInferenceStopped', () => { +import { test, expect } from 'vitest' +import { InferenceEvent } from './inferenceEvent'; + +test('testInferenceEventEnumContainsOnInferenceStopped', () => { expect(InferenceEvent.OnInferenceStopped).toBe('OnInferenceStopped'); }); diff --git a/core/src/types/message/messageEntity.test.ts b/core/src/types/message/messageEntity.test.ts index 1d41d129a..fd0663b5f 100644 --- a/core/src/types/message/messageEntity.test.ts +++ b/core/src/types/message/messageEntity.test.ts @@ -1,4 +1,5 @@ +import { it, expect } from 'vitest' import { MessageStatus } from './messageEntity'; it('should have correct values', () => { diff --git a/core/src/types/message/messageEvent.test.ts b/core/src/types/message/messageEvent.test.ts index 80a943bb1..92a965dab 100644 --- a/core/src/types/message/messageEvent.test.ts +++ b/core/src/types/message/messageEvent.test.ts @@ -1,7 +1,8 @@ - import { MessageEvent } from './messageEvent'; - - test('testOnMessageSentValue', () => { +import { test, expect } from 'vitest' +import { MessageEvent } from './messageEvent'; + +test('testOnMessageSentValue', () => { expect(MessageEvent.OnMessageSent).toBe('OnMessageSent'); }); diff --git a/core/src/types/message/messageRequestType.test.ts b/core/src/types/message/messageRequestType.test.ts index 41f53b2e0..bba9e0c1f 100644 --- a/core/src/types/message/messageRequestType.test.ts +++ b/core/src/types/message/messageRequestType.test.ts @@ -1,7 +1,8 @@ - import { MessageRequestType } from './messageRequestType'; - - test('testMessageRequestTypeEnumContainsThread', () => { +import { test, expect } from 'vitest' +import { MessageRequestType } from './messageRequestType'; + +test('testMessageRequestTypeEnumContainsThread', () => { expect(MessageRequestType.Thread).toBe('Thread'); }); diff --git a/core/src/types/miscellaneous/systemResourceInfo.test.ts b/core/src/types/miscellaneous/systemResourceInfo.test.ts index 35a459f0e..c586f2732 100644 --- a/core/src/types/miscellaneous/systemResourceInfo.test.ts +++ b/core/src/types/miscellaneous/systemResourceInfo.test.ts @@ -1,4 +1,5 @@ +import { it, expect } from 'vitest' import { SupportedPlatforms } from './systemResourceInfo'; it('should contain the correct values', () => { diff --git a/core/src/types/model/modelEntity.test.ts b/core/src/types/model/modelEntity.test.ts index 835bb2a75..332afd4ed 100644 --- a/core/src/types/model/modelEntity.test.ts +++ b/core/src/types/model/modelEntity.test.ts @@ -1,3 +1,4 @@ +import { test, expect } from 'vitest' import { Model, ModelSettingParams, ModelRuntimeParams } from '../model' import { InferenceEngine } from '../engine' diff --git a/core/src/types/model/modelEvent.test.ts b/core/src/types/model/modelEvent.test.ts index f9fa8cc6a..04ce0d833 100644 --- a/core/src/types/model/modelEvent.test.ts +++ b/core/src/types/model/modelEvent.test.ts @@ -1,7 +1,8 @@ - import { ModelEvent } from './modelEvent'; - - test('testOnModelInit', () => { +import { test, expect } from 'vitest' +import { ModelEvent } from './modelEvent'; + +test('testOnModelInit', () => { expect(ModelEvent.OnModelInit).toBe('OnModelInit'); }); diff --git a/core/src/types/setting/index.test.ts b/core/src/types/setting/index.test.ts index 699adfe4f..5ea92d340 100644 --- a/core/src/types/setting/index.test.ts +++ b/core/src/types/setting/index.test.ts @@ -1,5 +1,6 @@ - +import { it, expect } from 'vitest' +import './index' it('should not throw any errors', () => { - expect(() => require('./index')).not.toThrow(); -}); + expect(true).toBe(true) +}) diff --git a/core/src/types/setting/settingComponent.test.ts b/core/src/types/setting/settingComponent.test.ts index b11990bab..7dab9e720 100644 --- a/core/src/types/setting/settingComponent.test.ts +++ b/core/src/types/setting/settingComponent.test.ts @@ -1,7 +1,8 @@ +import { it, expect } from 'vitest' import * as SettingComponent from './settingComponent' it('should not throw any errors when importing settingComponent', () => { - expect(() => require('./settingComponent')).not.toThrow() + expect(true).toBe(true) }) it('should export SettingComponentProps type', () => { diff --git a/core/testRunner.js b/core/testRunner.js deleted file mode 100644 index b0d108160..000000000 --- a/core/testRunner.js +++ /dev/null @@ -1,10 +0,0 @@ -const jestRunner = require('jest-runner'); - -class EmptyTestFileRunner extends jestRunner.default { - async runTests(tests, watcher, onStart, onResult, onFailure, options) { - const nonEmptyTests = tests.filter(test => test.context.hasteFS.getSize(test.path) > 0); - return super.runTests(nonEmptyTests, watcher, onStart, onResult, onFailure, options); - } -} - -module.exports = EmptyTestFileRunner; \ No newline at end of file diff --git a/core/tsconfig.json b/core/tsconfig.json index 3c1e7f57a..2d21cbe4b 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -13,7 +13,7 @@ "declarationDir": "dist/types", "outDir": "dist", "importHelpers": true, - "types": ["jest", "node"] + "types": ["node"] }, "include": ["src"], "exclude": ["src/**/*.test.ts"] diff --git a/core/vitest.config.ts b/core/vitest.config.ts new file mode 100644 index 000000000..bf326d7f0 --- /dev/null +++ b/core/vitest.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config' +import { resolve } from 'path' + +export default defineConfig({ + test: { + environment: 'jsdom', + globals: true, + setupFiles: ['./src/test/setup.ts'], + coverage: { + reporter: ['text', 'json', 'html', 'lcov'], + include: ['src/**/*.{ts,tsx}'], + exclude: ['node_modules/', 'dist/', 'src/**/*.test.ts'] + }, + include: ['src/**/*.test.ts'], + exclude: ['node_modules/', 'dist/'] + }, + resolve: { + alias: { + '@': resolve(__dirname, './src') + } + } +}) \ No newline at end of file diff --git a/extensions/conversational-extension/jest.config.js b/extensions/conversational-extension/jest.config.js deleted file mode 100644 index 8bb37208d..000000000 --- a/extensions/conversational-extension/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -} diff --git a/extensions/conversational-extension/package.json b/extensions/conversational-extension/package.json index 26ba21b9d..8e3392ada 100644 --- a/extensions/conversational-extension/package.json +++ b/extensions/conversational-extension/package.json @@ -7,7 +7,6 @@ "author": "Jan ", "license": "MIT", "scripts": { - "test": "jest", "build": "rolldown -c rolldown.config.mjs", "build:publish": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install" }, diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 0dc931b28..000000000 --- a/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - projects: ['/core'], -} diff --git a/package.json b/package.json index b96823320..2bdaca4cc 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,10 @@ "lint": "yarn workspace @janhq/web-app lint", "dev": "yarn dev:tauri", "build": "yarn build:web && yarn build:tauri", - "test": "jest && yarn workspace @janhq/web-app test", - "test:coverage": "yarn test:coverage:jest && yarn test:coverage:vitest && yarn merge:coverage", - "test:coverage:jest": "jest --coverage --coverageDirectory=coverage/jest", - "test:coverage:vitest": "yarn workspace @janhq/web-app test:coverage", - "merge:coverage": "node scripts/merge-coverage.js", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "test:prepare": "yarn build:icon && yarn copy:assets:tauri && yarn build --no-bundle ", "test:e2e:linux": "yarn test:prepare && xvfb-run yarn workspace tests-e2-js test", "test:e2e:win32": "yarn test:prepare && yarn workspace tests-e2-js test", @@ -39,21 +38,19 @@ }, "devDependencies": { "@tauri-apps/cli": "^2.2.5", + "@vitest/coverage-v8": "^3.1.3", "concurrently": "^9.1.0", "cpx": "^1.5.0", "cross-env": "^7.0.3", + "happy-dom": "^15.11.6", "husky": "^9.1.5", - "istanbul-api": "^3.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.7", - "jest": "^30.0.3", - "jest-environment-jsdom": "^29.7.0", + "jsdom": "^26.1.0", "nyc": "^17.1.0", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", "tar": "^4.4.19", "unzipper": "^0.12.3", + "vitest": "^3.1.3", "wait-on": "^7.0.1" }, "version": "0.0.0", diff --git a/scripts/merge-coverage.js b/scripts/merge-coverage.js deleted file mode 100644 index 3f8f1cb8e..000000000 --- a/scripts/merge-coverage.js +++ /dev/null @@ -1,145 +0,0 @@ -const { createCoverageMap } = require('istanbul-lib-coverage') -const { createReporter } = require('istanbul-api') -const fs = require('fs') -const path = require('path') - -const coverageDir = path.join(__dirname, '../coverage') -const jestCoverage = path.join(coverageDir, 'jest/coverage-final.json') -const vitestCoverage = path.join(coverageDir, 'vitest/coverage-final.json') -const mergedDir = path.join(coverageDir, 'merged') - -function normalizePath(filePath, workspace) { - if (workspace === 'jest') { - return `[CORE] ${filePath}` - } else if (workspace === 'vitest') { - return `[WEB-APP] ${filePath}` - } - return filePath -} - -async function mergeCoverage() { - const map = createCoverageMap({}) - - console.log('šŸ” Checking coverage files...') - console.log('Jest coverage path:', jestCoverage) - console.log('Vitest coverage path:', vitestCoverage) - console.log('Jest file exists:', fs.existsSync(jestCoverage)) - console.log('Vitest file exists:', fs.existsSync(vitestCoverage)) - - // Load Jest coverage (core workspace) - if (fs.existsSync(jestCoverage)) { - const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8')) - console.log('Jest data keys:', Object.keys(jestData).length) - map.merge(jestData) - console.log('āœ“ Merged Jest coverage (core workspace)') - } else { - console.log('āŒ Jest coverage file not found') - } - - // Load Vitest coverage (web-app workspace) - if (fs.existsSync(vitestCoverage)) { - const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8')) - console.log('Vitest data keys:', Object.keys(vitestData).length) - map.merge(vitestData) - console.log('āœ“ Merged Vitest coverage (web-app workspace)') - } else { - console.log('āŒ Vitest coverage file not found') - } - - console.log('šŸ“Š Total files in coverage map:', map.files().length) - - // Create merged directory - if (!fs.existsSync(mergedDir)) { - fs.mkdirSync(mergedDir, { recursive: true }) - console.log('āœ“ Created merged directory') - } - - try { - console.log('šŸ”„ Generating reports...') - - const context = require('istanbul-lib-report').createContext({ - dir: mergedDir, - coverageMap: map, - }) - - const htmlReporter = require('istanbul-reports').create('html') - const lcovReporter = require('istanbul-reports').create('lcov') - const textReporter = require('istanbul-reports').create('text') - - // Generate reports - htmlReporter.execute(context) - lcovReporter.execute(context) - textReporter.execute(context) - - console.log('\nšŸ“Š Coverage reports merged successfully!') - console.log('šŸ“ HTML report: coverage/merged/index.html') - console.log('šŸ“ LCOV report: coverage/merged/lcov.info') - - // Check if files were created - if (fs.existsSync(mergedDir)) { - const mergedFiles = fs.readdirSync(mergedDir) - console.log('šŸ“ Files in merged directory:', mergedFiles) - } - } catch (error) { - console.error('āŒ Error generating reports:', error.message) - console.error('Stack trace:', error.stack) - throw error - } - - // Generate separate reports for each workspace - await generateWorkspaceReports() -} - -async function generateWorkspaceReports() { - // Generate separate core report - if (fs.existsSync(jestCoverage)) { - const coreMap = createCoverageMap({}) - const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8')) - coreMap.merge(jestData) - - const coreDir = path.join(coverageDir, 'core-only') - if (!fs.existsSync(coreDir)) { - fs.mkdirSync(coreDir, { recursive: true }) - } - - const coreContext = require('istanbul-lib-report').createContext({ - dir: coreDir, - coverageMap: coreMap, - }) - - const htmlReporter = require('istanbul-reports').create('html') - const textSummaryReporter = - require('istanbul-reports').create('text-summary') - - htmlReporter.execute(coreContext) - textSummaryReporter.execute(coreContext) - console.log('šŸ“ Core-only report: coverage/core-only/index.html') - } - - // Generate separate web-app report - if (fs.existsSync(vitestCoverage)) { - const webAppMap = createCoverageMap({}) - const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8')) - webAppMap.merge(vitestData) - - const webAppDir = path.join(coverageDir, 'web-app-only') - if (!fs.existsSync(webAppDir)) { - fs.mkdirSync(webAppDir, { recursive: true }) - } - - const webAppContext = require('istanbul-lib-report').createContext({ - dir: webAppDir, - coverageMap: webAppMap, - }) - - const htmlReporter = require('istanbul-reports').create('html') - const textSummaryReporter = - require('istanbul-reports').create('text-summary') - - htmlReporter.execute(webAppContext) - textSummaryReporter.execute(webAppContext) - console.log('šŸ“ Web-app-only report: coverage/web-app-only/index.html') - } -} - -mergeCoverage().catch(console.error) diff --git a/src-tauri/build-utils/buildAppImage.sh b/src-tauri/build-utils/buildAppImage.sh index aae2ec283..10b92121c 100755 --- a/src-tauri/build-utils/buildAppImage.sh +++ b/src-tauri/build-utils/buildAppImage.sh @@ -20,8 +20,6 @@ fi # bundle additional resources in the AppDir without pulling in their dependencies cp ./src-tauri/resources/bin/bun $APP_DIR/usr/bin/bun mkdir -p $LIB_DIR/engines -cp -f ./src-tauri/binaries/deps/*.so* $LIB_DIR/ -cp -f ./src-tauri/binaries/*.so* $LIB_DIR/ # remove appimage generated by tauri build APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1) diff --git a/src-tauri/tauri.linux.conf.json b/src-tauri/tauri.linux.conf.json index e0b916146..48411fd3b 100644 --- a/src-tauri/tauri.linux.conf.json +++ b/src-tauri/tauri.linux.conf.json @@ -11,7 +11,6 @@ "deb": { "files": { "usr/bin/bun": "resources/bin/bun", - "usr/lib/Jan/binaries": "binaries/deps", "usr/lib/Jan/resources/lib/libvulkan.so": "resources/lib/libvulkan.so" } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..58d4bceeb --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + // Core package - use its own vitest config + './core', + + // Web-app package - use its own vitest config + './web-app' + ] + } +}) \ No newline at end of file diff --git a/web-app/src/test/setup.ts b/web-app/src/test/setup.ts index c126d92fa..1d36edc5c 100644 --- a/web-app/src/test/setup.ts +++ b/web-app/src/test/setup.ts @@ -1,10 +1,25 @@ -import { expect, afterEach } from 'vitest' +import { expect, afterEach, vi } from 'vitest' import { cleanup } from '@testing-library/react' import * as matchers from '@testing-library/jest-dom/matchers' // extends Vitest's expect method with methods from react-testing-library expect.extend(matchers) +// Mock window.matchMedia for useMediaQuery tests +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}) + // runs a cleanup after each test case (e.g. clearing jsdom) afterEach(() => { cleanup() diff --git a/web-app/vitest.config.ts b/web-app/vitest.config.ts index 36d8b6171..13d5338a1 100644 --- a/web-app/vitest.config.ts +++ b/web-app/vitest.config.ts @@ -9,6 +9,11 @@ export default defineConfig({ setupFiles: ['./src/test/setup.ts'], globals: true, css: true, + coverage: { + reporter: ['text', 'json', 'html', 'lcov'], + include: ['src/**/*.{ts,tsx}'], + exclude: ['node_modules/', 'dist/', 'src/**/*.test.ts', 'src/**/*.test.tsx', 'src/test/**/*'] + }, }, resolve: { alias: {