From 846efb31264059852b28930fdbaa0dcc6a125aa0 Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 6 Sep 2024 11:14:28 +0700 Subject: [PATCH] test: add core modules test cases (#3498) * chore: add core module test cases * chore: fix tests * chore: add code coverage report * chore: split coverage step * chore: split coverage step * Update jan-electron-linter-and-test.yml * Update jan-electron-linter-and-test.yml * Update jan-electron-linter-and-test.yml * chore: update tests * chore: add web utils test cases * chore: add restful and helper tests * chore: add tests --- .../jan-electron-linter-and-test.yml | 74 +++++++++++ .gitignore | 2 + Makefile | 2 +- core/jest.config.js | 1 + core/package.json | 4 +- core/src/browser/core.test.ts | 98 ++++++++++++++ core/src/browser/events.test.ts | 37 ++++++ core/src/browser/extension.test.ts | 46 +++++++ .../extensions/engines/helpers/sse.test.ts | 60 +++++++++ core/src/browser/index.test.ts | 32 +++++ core/src/node/api/common/adapter.test.ts | 10 ++ core/src/node/api/common/handler.test.ts | 25 ++++ core/src/node/api/processors/app.test.ts | 40 ++++++ core/src/node/api/processors/download.test.ts | 59 +++++++++ .../src/node/api/processors/extension.test.ts | 9 ++ core/src/node/api/processors/fs.test.ts | 18 +++ core/src/node/api/processors/fsExt.test.ts | 34 +++++ .../src/node/api/processors/processor.test.ts | 0 .../src/node/api/restful/app/download.test.ts | 62 +++++++++ .../src/node/api/restful/app/handlers.test.ts | 16 +++ core/src/node/api/restful/common.test.ts | 21 +++ .../api/restful/helper/configuration.test.ts | 24 ++++ core/src/node/api/restful/v1.test.ts | 16 +++ core/src/node/extension/extension.test.ts | 122 ++++++++++++++++++ core/src/node/extension/manager.test.ts | 28 ++++ core/src/node/extension/store.test.ts | 43 ++++++ core/src/node/helper/config.test.ts | 14 ++ core/src/node/helper/download.test.ts | 11 ++ core/src/node/helper/logger.test.ts | 47 +++++++ core/src/node/helper/module.test.ts | 23 ++++ core/src/node/helper/path.test.ts | 29 +++++ core/src/node/helper/resource.test.ts | 15 +++ .../types/assistant/assistantEvent.test.ts | 7 + core/src/types/file/index.ts | 4 +- core/testRunner.js | 10 ++ core/tests/node/path.test.ts | 12 -- core/tsconfig.json | 1 + jest.config.js | 2 +- web/utils/Stack.test.ts | 35 +++++ web/utils/componentSettings.test.ts | 22 ++++ web/utils/datetime.test.ts | 27 ++++ web/utils/jsonToCssVariables.test.ts | 17 +++ web/utils/memory.test.ts | 2 - web/utils/predefinedComponent.test.ts | 18 +++ web/utils/thread.test.ts | 9 ++ web/utils/titleUtils.test.ts | 25 ++++ 46 files changed, 1194 insertions(+), 19 deletions(-) create mode 100644 core/src/browser/core.test.ts create mode 100644 core/src/browser/events.test.ts create mode 100644 core/src/browser/extension.test.ts create mode 100644 core/src/browser/extensions/engines/helpers/sse.test.ts create mode 100644 core/src/browser/index.test.ts create mode 100644 core/src/node/api/common/adapter.test.ts create mode 100644 core/src/node/api/common/handler.test.ts create mode 100644 core/src/node/api/processors/app.test.ts create mode 100644 core/src/node/api/processors/download.test.ts create mode 100644 core/src/node/api/processors/extension.test.ts create mode 100644 core/src/node/api/processors/fs.test.ts create mode 100644 core/src/node/api/processors/fsExt.test.ts create mode 100644 core/src/node/api/processors/processor.test.ts create mode 100644 core/src/node/api/restful/app/download.test.ts create mode 100644 core/src/node/api/restful/app/handlers.test.ts create mode 100644 core/src/node/api/restful/common.test.ts create mode 100644 core/src/node/api/restful/helper/configuration.test.ts create mode 100644 core/src/node/api/restful/v1.test.ts create mode 100644 core/src/node/extension/extension.test.ts create mode 100644 core/src/node/extension/manager.test.ts create mode 100644 core/src/node/extension/store.test.ts create mode 100644 core/src/node/helper/config.test.ts create mode 100644 core/src/node/helper/download.test.ts create mode 100644 core/src/node/helper/logger.test.ts create mode 100644 core/src/node/helper/module.test.ts create mode 100644 core/src/node/helper/path.test.ts create mode 100644 core/src/node/helper/resource.test.ts create mode 100644 core/src/types/assistant/assistantEvent.test.ts create mode 100644 core/testRunner.js delete mode 100644 core/tests/node/path.test.ts create mode 100644 web/utils/Stack.test.ts create mode 100644 web/utils/componentSettings.test.ts create mode 100644 web/utils/datetime.test.ts create mode 100644 web/utils/jsonToCssVariables.test.ts create mode 100644 web/utils/predefinedComponent.test.ts create mode 100644 web/utils/thread.test.ts create mode 100644 web/utils/titleUtils.test.ts diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml index 3a95e804e..529739e24 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-electron-linter-and-test.yml @@ -37,6 +37,30 @@ on: - '!README.md' jobs: + + base_branch_cov: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.base_ref }} + - name: Use Node.js v20.9.0 + uses: actions/setup-node@v3 + with: + node-version: v20.9.0 + + - name: Install dependencies + run: yarn + + - name: Run test coverage + run: yarn test:coverage + + - name: Upload code coverage for ref branch + uses: actions/upload-artifact@v3 + with: + name: ref-lcov.info + path: ./coverage/lcov.info + test-on-macos: if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: [self-hosted, macOS, macos-desktop] @@ -292,6 +316,56 @@ jobs: TURBO_TEAM: 'linux' TURBO_TOKEN: '${{ secrets.TURBO_TOKEN }}' + coverage-check: + runs-on: [self-hosted, Linux, ubuntu-desktop] + needs: base_branch_cov + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch' + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Installing node + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: 'Cleanup cache' + continue-on-error: true + run: | + rm -rf ~/jan + make clean + + - name: Download code coverage report from base branch + uses: actions/download-artifact@v3 + with: + name: ref-lcov.info + + - name: Linter and test coverage + run: | + export DISPLAY=$(w -h | awk 'NR==1 {print $2}') + echo -e "Display ID: $DISPLAY" + npm config set registry ${{ secrets.NPM_PROXY }} --global + yarn config set registry ${{ secrets.NPM_PROXY }} --global + make lint + yarn build:test + yarn test:coverage + env: + TURBO_API: '${{ secrets.TURBO_API }}' + TURBO_TEAM: 'linux' + TURBO_TOKEN: '${{ secrets.TURBO_TOKEN }}' + + - name: Generate Code Coverage report + id: code-coverage + uses: barecheck/code-coverage-action@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + lcov-file: "./coverage/lcov.info" + base-lcov-file: "./lcov.info" + send-summary-comment: true + show-annotations: "warning" + test-on-ubuntu-pr-target: runs-on: [self-hosted, Linux, ubuntu-desktop] if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository diff --git a/.gitignore b/.gitignore index 4c0884dec..f9b1dab66 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ extensions/*-extension/bin/vulkaninfo .turbo electron/test-data electron/test-results +core/test_results.html +coverage diff --git a/Makefile b/Makefile index 1687f8bbe..0228c52d7 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ endif # Testing test: lint yarn build:test - yarn test:unit + yarn test:coverage yarn test # Builds and publishes the app diff --git a/core/jest.config.js b/core/jest.config.js index c18f55091..6c805f1c9 100644 --- a/core/jest.config.js +++ b/core/jest.config.js @@ -4,4 +4,5 @@ module.exports = { moduleNameMapper: { '@/(.*)': '/src/$1', }, + runner: './testRunner.js', } diff --git a/core/package.json b/core/package.json index 9e4d8d69a..ac3305014 100644 --- a/core/package.json +++ b/core/package.json @@ -46,6 +46,8 @@ "eslint": "8.57.0", "eslint-plugin-jest": "^27.9.0", "jest": "^29.7.0", + "jest-junit": "^16.0.0", + "jest-runner": "^29.7.0", "rimraf": "^3.0.2", "rollup": "^2.38.5", "rollup-plugin-commonjs": "^9.1.8", @@ -53,7 +55,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-typescript2": "^0.36.0", - "ts-jest": "^29.1.2", + "ts-jest": "^29.2.5", "tslib": "^2.6.2", "typescript": "^5.3.3" }, diff --git a/core/src/browser/core.test.ts b/core/src/browser/core.test.ts new file mode 100644 index 000000000..84250888e --- /dev/null +++ b/core/src/browser/core.test.ts @@ -0,0 +1,98 @@ +import { openExternalUrl } from './core'; +import { joinPath } from './core'; +import { openFileExplorer } from './core'; +import { getJanDataFolderPath } from './core'; +import { abortDownload } from './core'; +import { getFileSize } from './core'; +import { executeOnMain } from './core'; + +it('should open external url', async () => { + const url = 'http://example.com'; + globalThis.core = { + api: { + openExternalUrl: jest.fn().mockResolvedValue('opened') + } + }; + const result = await openExternalUrl(url); + expect(globalThis.core.api.openExternalUrl).toHaveBeenCalledWith(url); + expect(result).toBe('opened'); +}); + + +it('should join paths', async () => { + const paths = ['/path/one', '/path/two']; + globalThis.core = { + api: { + joinPath: jest.fn().mockResolvedValue('/path/one/path/two') + } + }; + const result = await joinPath(paths); + expect(globalThis.core.api.joinPath).toHaveBeenCalledWith(paths); + expect(result).toBe('/path/one/path/two'); +}); + + +it('should open file explorer', async () => { + const path = '/path/to/open'; + globalThis.core = { + api: { + openFileExplorer: jest.fn().mockResolvedValue('opened') + } + }; + const result = await openFileExplorer(path); + expect(globalThis.core.api.openFileExplorer).toHaveBeenCalledWith(path); + expect(result).toBe('opened'); +}); + + +it('should get jan data folder path', async () => { + globalThis.core = { + api: { + getJanDataFolderPath: jest.fn().mockResolvedValue('/path/to/jan/data') + } + }; + const result = await getJanDataFolderPath(); + expect(globalThis.core.api.getJanDataFolderPath).toHaveBeenCalled(); + expect(result).toBe('/path/to/jan/data'); +}); + + +it('should abort download', async () => { + const fileName = 'testFile'; + globalThis.core = { + api: { + abortDownload: jest.fn().mockResolvedValue('aborted') + } + }; + const result = await abortDownload(fileName); + expect(globalThis.core.api.abortDownload).toHaveBeenCalledWith(fileName); + expect(result).toBe('aborted'); +}); + + +it('should get file size', async () => { + const url = 'http://example.com/file'; + globalThis.core = { + api: { + getFileSize: jest.fn().mockResolvedValue(1024) + } + }; + const result = await getFileSize(url); + expect(globalThis.core.api.getFileSize).toHaveBeenCalledWith(url); + expect(result).toBe(1024); +}); + + +it('should execute function on main process', async () => { + const extension = 'testExtension'; + const method = 'testMethod'; + const args = ['arg1', 'arg2']; + globalThis.core = { + api: { + invokeExtensionFunc: jest.fn().mockResolvedValue('result') + } + }; + const result = await executeOnMain(extension, method, ...args); + expect(globalThis.core.api.invokeExtensionFunc).toHaveBeenCalledWith(extension, method, ...args); + expect(result).toBe('result'); +}); diff --git a/core/src/browser/events.test.ts b/core/src/browser/events.test.ts new file mode 100644 index 000000000..23b4d78d9 --- /dev/null +++ b/core/src/browser/events.test.ts @@ -0,0 +1,37 @@ +import { events } from './events'; +import { jest } from '@jest/globals'; + +it('should emit an event', () => { + const mockObject = { key: 'value' }; + globalThis.core = { + events: { + emit: jest.fn() + } + }; + events.emit('testEvent', mockObject); + expect(globalThis.core.events.emit).toHaveBeenCalledWith('testEvent', mockObject); +}); + + +it('should remove an observer for an event', () => { + const mockHandler = jest.fn(); + globalThis.core = { + events: { + off: jest.fn() + } + }; + events.off('testEvent', mockHandler); + expect(globalThis.core.events.off).toHaveBeenCalledWith('testEvent', mockHandler); +}); + + +it('should add an observer for an event', () => { + const mockHandler = jest.fn(); + globalThis.core = { + events: { + on: jest.fn() + } + }; + events.on('testEvent', mockHandler); + expect(globalThis.core.events.on).toHaveBeenCalledWith('testEvent', mockHandler); +}); diff --git a/core/src/browser/extension.test.ts b/core/src/browser/extension.test.ts new file mode 100644 index 000000000..6c1cd8579 --- /dev/null +++ b/core/src/browser/extension.test.ts @@ -0,0 +1,46 @@ +import { BaseExtension } from './extension' + +class TestBaseExtension extends BaseExtension { + onLoad(): void {} + onUnload(): void {} +} + +describe('BaseExtension', () => { + let baseExtension: TestBaseExtension + + beforeEach(() => { + baseExtension = new TestBaseExtension('https://example.com', 'TestExtension') + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + it('should have the correct properties', () => { + expect(baseExtension.name).toBe('TestExtension') + expect(baseExtension.productName).toBeUndefined() + expect(baseExtension.url).toBe('https://example.com') + expect(baseExtension.active).toBeUndefined() + expect(baseExtension.description).toBeUndefined() + expect(baseExtension.version).toBeUndefined() + }) + + it('should return undefined for type()', () => { + expect(baseExtension.type()).toBeUndefined() + }) + + it('should have abstract methods onLoad() and onUnload()', () => { + expect(baseExtension.onLoad).toBeDefined() + expect(baseExtension.onUnload).toBeDefined() + }) + + it('should have installationState() return "NotRequired"', async () => { + const installationState = await baseExtension.installationState() + expect(installationState).toBe('NotRequired') + }) + + it('should install the extension', async () => { + await baseExtension.install() + // Add your assertions here + }) +}) diff --git a/core/src/browser/extensions/engines/helpers/sse.test.ts b/core/src/browser/extensions/engines/helpers/sse.test.ts new file mode 100644 index 000000000..cff5b93b3 --- /dev/null +++ b/core/src/browser/extensions/engines/helpers/sse.test.ts @@ -0,0 +1,60 @@ +import { lastValueFrom, Observable } from 'rxjs' +import { requestInference } from './sse' + +describe('requestInference', () => { + it('should send a request to the inference server and return an Observable', () => { + // Mock the fetch function + const mockFetch: any = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ choices: [{ message: { content: 'Generated response' } }] }), + headers: new Headers(), + redirected: false, + status: 200, + statusText: 'OK', + // Add other required properties here + }) + ) + jest.spyOn(global, 'fetch').mockImplementation(mockFetch) + + // Define the test inputs + const inferenceUrl = 'https://inference-server.com' + const requestBody = { message: 'Hello' } + const model = { id: 'model-id', parameters: { stream: false } } + + // Call the function + const result = requestInference(inferenceUrl, requestBody, model) + + // Assert the expected behavior + expect(result).toBeInstanceOf(Observable) + expect(lastValueFrom(result)).resolves.toEqual('Generated response') + }) + + it('returns 401 error', () => { + // Mock the fetch function + const mockFetch: any = jest.fn(() => + Promise.resolve({ + ok: false, + json: () => Promise.resolve({ error: { message: 'Wrong API Key', code: 'invalid_api_key' } }), + headers: new Headers(), + redirected: false, + status: 401, + statusText: 'invalid_api_key', + // Add other required properties here + }) + ) + jest.spyOn(global, 'fetch').mockImplementation(mockFetch) + + // Define the test inputs + const inferenceUrl = 'https://inference-server.com' + const requestBody = { message: 'Hello' } + const model = { id: 'model-id', parameters: { stream: false } } + + // Call the function + const result = requestInference(inferenceUrl, requestBody, model) + + // Assert the expected behavior + expect(result).toBeInstanceOf(Observable) + expect(lastValueFrom(result)).rejects.toEqual({ message: 'Wrong API Key', code: 'invalid_api_key' }) + }) +}) diff --git a/core/src/browser/index.test.ts b/core/src/browser/index.test.ts new file mode 100644 index 000000000..339cd9046 --- /dev/null +++ b/core/src/browser/index.test.ts @@ -0,0 +1,32 @@ +import * as Core from './core'; +import * as Events from './events'; +import * as FileSystem from './fs'; +import * as Extension from './extension'; +import * as Extensions from './extensions'; +import * as Tools from './tools'; + +describe('Module Tests', () => { + it('should export Core module', () => { + expect(Core).toBeDefined(); + }); + + it('should export Event module', () => { + expect(Events).toBeDefined(); + }); + + it('should export Filesystem module', () => { + expect(FileSystem).toBeDefined(); + }); + + it('should export Extension module', () => { + expect(Extension).toBeDefined(); + }); + + it('should export all base extensions', () => { + expect(Extensions).toBeDefined(); + }); + + it('should export all base tools', () => { + expect(Tools).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/core/src/node/api/common/adapter.test.ts b/core/src/node/api/common/adapter.test.ts new file mode 100644 index 000000000..38fd2857f --- /dev/null +++ b/core/src/node/api/common/adapter.test.ts @@ -0,0 +1,10 @@ +import { RequestAdapter } from './adapter'; + +it('should return undefined for unknown route', () => { + const adapter = new RequestAdapter(); + const route = 'unknownRoute'; + + const result = adapter.process(route, 'arg1', 'arg2'); + + expect(result).toBeUndefined(); +}); diff --git a/core/src/node/api/common/handler.test.ts b/core/src/node/api/common/handler.test.ts new file mode 100644 index 000000000..bd55d41cc --- /dev/null +++ b/core/src/node/api/common/handler.test.ts @@ -0,0 +1,25 @@ +import { CoreRoutes } from '../../../types/api'; +import { RequestHandler } from './handler'; +import { RequestAdapter } from './adapter'; + +it('should not call handler if CoreRoutes is empty', () => { + const mockHandler = jest.fn(); + const mockObserver = jest.fn(); + const requestHandler = new RequestHandler(mockHandler, mockObserver); + + CoreRoutes.length = 0; // Ensure CoreRoutes is empty + + requestHandler.handle(); + + expect(mockHandler).not.toHaveBeenCalled(); +}); + + +it('should initialize handler and adapter correctly', () => { + const mockHandler = jest.fn(); + const mockObserver = jest.fn(); + const requestHandler = new RequestHandler(mockHandler, mockObserver); + + expect(requestHandler.handler).toBe(mockHandler); + expect(requestHandler.adapter).toBeInstanceOf(RequestAdapter); +}); diff --git a/core/src/node/api/processors/app.test.ts b/core/src/node/api/processors/app.test.ts new file mode 100644 index 000000000..3ada5df1e --- /dev/null +++ b/core/src/node/api/processors/app.test.ts @@ -0,0 +1,40 @@ +import { App } from './app'; + +it('should call stopServer', () => { + const app = new App(); + const stopServerMock = jest.fn().mockResolvedValue('Server stopped'); + jest.mock('@janhq/server', () => ({ + stopServer: stopServerMock + })); + const result = app.stopServer(); + expect(stopServerMock).toHaveBeenCalled(); +}); + +it('should correctly retrieve basename', () => { + const app = new App(); + const result = app.baseName('/path/to/file.txt'); + expect(result).toBe('file.txt'); +}); + +it('should correctly identify subdirectories', () => { + const app = new App(); + const basePath = process.platform === 'win32' ? 'C:\\path\\to' : '/path/to'; + const subPath = process.platform === 'win32' ? 'C:\\path\\to\\subdir' : '/path/to/subdir'; + const result = app.isSubdirectory(basePath, subPath); + expect(result).toBe(true); +}); + +it('should correctly join multiple paths', () => { + const app = new App(); + const result = app.joinPath(['path', 'to', 'file']); + const expectedPath = process.platform === 'win32' ? 'path\\to\\file' : 'path/to/file'; + expect(result).toBe(expectedPath); +}); + +it('should call correct function with provided arguments using process method', () => { + const app = new App(); + const mockFunc = jest.fn(); + app.joinPath = mockFunc; + app.process('joinPath', ['path1', 'path2']); + expect(mockFunc).toHaveBeenCalledWith(['path1', 'path2']); +}); diff --git a/core/src/node/api/processors/download.test.ts b/core/src/node/api/processors/download.test.ts new file mode 100644 index 000000000..1dc0eefb8 --- /dev/null +++ b/core/src/node/api/processors/download.test.ts @@ -0,0 +1,59 @@ +import { Downloader } from './download'; +import { DownloadEvent } from '../../../types/api'; +import { DownloadManager } from '../../helper/download'; + +it('should handle getFileSize errors correctly', async () => { + const observer = jest.fn(); + const url = 'http://example.com/file'; + + const downloader = new Downloader(observer); + const requestMock = jest.fn((options, callback) => { + callback(new Error('Test error'), null); + }); + jest.mock('request', () => requestMock); + + await expect(downloader.getFileSize(observer, url)).rejects.toThrow('Test error'); +}); + + +it('should pause download correctly', () => { + const observer = jest.fn(); + const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file'; + + const downloader = new Downloader(observer); + const pauseMock = jest.fn(); + DownloadManager.instance.networkRequests[fileName] = { pause: pauseMock }; + + downloader.pauseDownload(observer, fileName); + + expect(pauseMock).toHaveBeenCalled(); +}); + +it('should resume download correctly', () => { + const observer = jest.fn(); + const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file'; + + const downloader = new Downloader(observer); + const resumeMock = jest.fn(); + DownloadManager.instance.networkRequests[fileName] = { resume: resumeMock }; + + downloader.resumeDownload(observer, fileName); + + expect(resumeMock).toHaveBeenCalled(); +}); + +it('should handle aborting a download correctly', () => { + const observer = jest.fn(); + const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file'; + + const downloader = new Downloader(observer); + const abortMock = jest.fn(); + DownloadManager.instance.networkRequests[fileName] = { abort: abortMock }; + + downloader.abortDownload(observer, fileName); + + expect(abortMock).toHaveBeenCalled(); + expect(observer).toHaveBeenCalledWith(DownloadEvent.onFileDownloadError, expect.objectContaining({ + error: 'aborted' + })); +}); diff --git a/core/src/node/api/processors/extension.test.ts b/core/src/node/api/processors/extension.test.ts new file mode 100644 index 000000000..917883499 --- /dev/null +++ b/core/src/node/api/processors/extension.test.ts @@ -0,0 +1,9 @@ +import { Extension } from './extension'; + +it('should call function associated with key in process method', () => { + const mockFunc = jest.fn(); + const extension = new Extension(); + (extension as any).testKey = mockFunc; + extension.process('testKey', 'arg1', 'arg2'); + expect(mockFunc).toHaveBeenCalledWith('arg1', 'arg2'); +}); diff --git a/core/src/node/api/processors/fs.test.ts b/core/src/node/api/processors/fs.test.ts new file mode 100644 index 000000000..3cac2e2ff --- /dev/null +++ b/core/src/node/api/processors/fs.test.ts @@ -0,0 +1,18 @@ +import { FileSystem } from './fs'; + +it('should throw an error when the route does not exist in process', async () => { + const fileSystem = new FileSystem(); + await expect(fileSystem.process('nonExistentRoute', 'arg1')).rejects.toThrow(); +}); + + +it('should throw an error for invalid argument in mkdir', async () => { + const fileSystem = new FileSystem(); + expect(() => fileSystem.mkdir(123)).toThrow('mkdir error: Invalid argument [123]'); +}); + + +it('should throw an error for invalid argument in rm', async () => { + const fileSystem = new FileSystem(); + expect(() => fileSystem.rm(123)).toThrow('rm error: Invalid argument [123]'); +}); diff --git a/core/src/node/api/processors/fsExt.test.ts b/core/src/node/api/processors/fsExt.test.ts new file mode 100644 index 000000000..bfc54897a --- /dev/null +++ b/core/src/node/api/processors/fsExt.test.ts @@ -0,0 +1,34 @@ +import { FSExt } from './fsExt'; +import { defaultAppConfig } from '../../helper'; + +it('should handle errors in writeBlob', () => { + const fsExt = new FSExt(); + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + fsExt.writeBlob('invalid-path', 'data'); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); +}); + +it('should call correct function in process method', () => { + const fsExt = new FSExt(); + const mockFunction = jest.fn(); + (fsExt as any).mockFunction = mockFunction; + fsExt.process('mockFunction', 'arg1', 'arg2'); + expect(mockFunction).toHaveBeenCalledWith('arg1', 'arg2'); +}); + + +it('should return correct user home path', () => { + const fsExt = new FSExt(); + const userHomePath = fsExt.getUserHomePath(); + expect(userHomePath).toBe(defaultAppConfig().data_folder); +}); + + + +it('should return empty array when no files are provided', async () => { + const fsExt = new FSExt(); + const result = await fsExt.getGgufFiles([]); + expect(result.supportedFiles).toEqual([]); + expect(result.unsupportedFiles).toEqual([]); +}); diff --git a/core/src/node/api/processors/processor.test.ts b/core/src/node/api/processors/processor.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/core/src/node/api/restful/app/download.test.ts b/core/src/node/api/restful/app/download.test.ts new file mode 100644 index 000000000..b2af1bb0d --- /dev/null +++ b/core/src/node/api/restful/app/download.test.ts @@ -0,0 +1,62 @@ +import { HttpServer } from '../../HttpServer' +import { DownloadManager } from '../../../helper/download' + +describe('downloadRouter', () => { + let app: HttpServer + + beforeEach(() => { + app = { + register: jest.fn(), + post: jest.fn(), + get: jest.fn(), + patch: jest.fn(), + put: jest.fn(), + delete: jest.fn(), + } + }) + + it('should return download progress for a given modelId', async () => { + const modelId = '123' + const downloadProgress = { progress: 50 } + + DownloadManager.instance.downloadProgressMap[modelId] = downloadProgress as any + + const req = { params: { modelId } } + const res = { + status: jest.fn(), + send: jest.fn(), + } + + jest.spyOn(app, 'get').mockImplementation((path, handler) => { + if (path === `/download/getDownloadProgress/${modelId}`) { + res.status(200) + res.send(downloadProgress) + } + }) + + app.get(`/download/getDownloadProgress/${modelId}`, req as any) + expect(res.status).toHaveBeenCalledWith(200) + expect(res.send).toHaveBeenCalledWith(downloadProgress) + }) + + it('should return 404 if download progress is not found', async () => { + const modelId = '123' + + const req = { params: { modelId } } + const res = { + status: jest.fn(), + send: jest.fn(), + } + + + jest.spyOn(app, 'get').mockImplementation((path, handler) => { + if (path === `/download/getDownloadProgress/${modelId}`) { + res.status(404) + res.send({ message: 'Download progress not found' }) + } + }) + app.get(`/download/getDownloadProgress/${modelId}`, req as any) + expect(res.status).toHaveBeenCalledWith(404) + expect(res.send).toHaveBeenCalledWith({ message: 'Download progress not found' }) + }) +}) diff --git a/core/src/node/api/restful/app/handlers.test.ts b/core/src/node/api/restful/app/handlers.test.ts new file mode 100644 index 000000000..680623d86 --- /dev/null +++ b/core/src/node/api/restful/app/handlers.test.ts @@ -0,0 +1,16 @@ +// +import { jest } from '@jest/globals'; + +import { HttpServer } from '../../HttpServer'; +import { handleRequests } from './handlers'; +import { Handler, RequestHandler } from '../../common/handler'; + +it('should initialize RequestHandler and call handle', () => { + const mockHandle = jest.fn(); + jest.spyOn(RequestHandler.prototype, 'handle').mockImplementation(mockHandle); + + const mockApp = { post: jest.fn() }; + handleRequests(mockApp as unknown as HttpServer); + + expect(mockHandle).toHaveBeenCalled(); +}); diff --git a/core/src/node/api/restful/common.test.ts b/core/src/node/api/restful/common.test.ts new file mode 100644 index 000000000..b40f6606f --- /dev/null +++ b/core/src/node/api/restful/common.test.ts @@ -0,0 +1,21 @@ + +import { commonRouter } from './common'; +import { JanApiRouteConfiguration } from './helper/configuration'; + +test('commonRouter sets up routes for each key in JanApiRouteConfiguration', async () => { + const mockHttpServer = { + get: jest.fn(), + post: jest.fn(), + patch: jest.fn(), + put: jest.fn(), + delete: jest.fn(), + }; + await commonRouter(mockHttpServer as any); + + const expectedRoutes = Object.keys(JanApiRouteConfiguration); + expectedRoutes.forEach((key) => { + expect(mockHttpServer.get).toHaveBeenCalledWith(`/${key}`, expect.any(Function)); + expect(mockHttpServer.get).toHaveBeenCalledWith(`/${key}/:id`, expect.any(Function)); + expect(mockHttpServer.delete).toHaveBeenCalledWith(`/${key}/:id`, expect.any(Function)); + }); +}); diff --git a/core/src/node/api/restful/helper/configuration.test.ts b/core/src/node/api/restful/helper/configuration.test.ts new file mode 100644 index 000000000..ae002312a --- /dev/null +++ b/core/src/node/api/restful/helper/configuration.test.ts @@ -0,0 +1,24 @@ +import { JanApiRouteConfiguration } from './configuration' + +describe('JanApiRouteConfiguration', () => { + it('should have the correct models configuration', () => { + const modelsConfig = JanApiRouteConfiguration.models; + expect(modelsConfig.dirName).toBe('models'); + expect(modelsConfig.metadataFileName).toBe('model.json'); + expect(modelsConfig.delete.object).toBe('model'); + }); + + it('should have the correct assistants configuration', () => { + const assistantsConfig = JanApiRouteConfiguration.assistants; + expect(assistantsConfig.dirName).toBe('assistants'); + expect(assistantsConfig.metadataFileName).toBe('assistant.json'); + expect(assistantsConfig.delete.object).toBe('assistant'); + }); + + it('should have the correct threads configuration', () => { + const threadsConfig = JanApiRouteConfiguration.threads; + expect(threadsConfig.dirName).toBe('threads'); + expect(threadsConfig.metadataFileName).toBe('thread.json'); + expect(threadsConfig.delete.object).toBe('thread'); + }); +}); \ No newline at end of file diff --git a/core/src/node/api/restful/v1.test.ts b/core/src/node/api/restful/v1.test.ts new file mode 100644 index 000000000..8e22496e9 --- /dev/null +++ b/core/src/node/api/restful/v1.test.ts @@ -0,0 +1,16 @@ + +import { v1Router } from './v1'; +import { commonRouter } from './common'; + +test('should define v1Router function', () => { + expect(v1Router).toBeDefined(); +}); + +test('should register commonRouter', () => { + const mockApp = { + register: jest.fn(), + }; + v1Router(mockApp as any); + expect(mockApp.register).toHaveBeenCalledWith(commonRouter); +}); + diff --git a/core/src/node/extension/extension.test.ts b/core/src/node/extension/extension.test.ts new file mode 100644 index 000000000..c43b5c0cb --- /dev/null +++ b/core/src/node/extension/extension.test.ts @@ -0,0 +1,122 @@ +import Extension from './extension'; +import { join } from 'path'; +import 'pacote'; + +it('should set active and call emitUpdate', () => { + const extension = new Extension(); + extension.emitUpdate = jest.fn(); + + extension.setActive(true); + + expect(extension._active).toBe(true); + expect(extension.emitUpdate).toHaveBeenCalled(); +}); + + +it('should return correct specifier', () => { + const origin = 'test-origin'; + const options = { version: '1.0.0' }; + const extension = new Extension(origin, options); + + expect(extension.specifier).toBe('test-origin@1.0.0'); +}); + + +it('should set origin and installOptions in constructor', () => { + const origin = 'test-origin'; + const options = { someOption: true }; + const extension = new Extension(origin, options); + + expect(extension.origin).toBe(origin); + expect(extension.installOptions.someOption).toBe(true); + expect(extension.installOptions.fullMetadata).toBe(true); // default option +}); + +it('should install extension and set url', async () => { + const origin = 'test-origin'; + const options = {}; + const extension = new Extension(origin, options); + + const mockManifest = { + name: 'test-name', + productName: 'Test Product', + version: '1.0.0', + main: 'index.js', + description: 'Test description' + }; + + jest.mock('pacote', () => ({ + manifest: jest.fn().mockResolvedValue(mockManifest), + extract: jest.fn().mockResolvedValue(null) + })); + + extension.emitUpdate = jest.fn(); + await extension._install(); + + expect(extension.url).toBe('extension://test-name/index.js'); + expect(extension.emitUpdate).toHaveBeenCalled(); +}); + + +it('should call all listeners in emitUpdate', () => { + const extension = new Extension(); + const callback1 = jest.fn(); + const callback2 = jest.fn(); + + extension.subscribe('listener1', callback1); + extension.subscribe('listener2', callback2); + + extension.emitUpdate(); + + expect(callback1).toHaveBeenCalledWith(extension); + expect(callback2).toHaveBeenCalledWith(extension); +}); + + +it('should remove listener in unsubscribe', () => { + const extension = new Extension(); + const callback = jest.fn(); + + extension.subscribe('testListener', callback); + extension.unsubscribe('testListener'); + + expect(extension.listeners['testListener']).toBeUndefined(); +}); + + +it('should add listener in subscribe', () => { + const extension = new Extension(); + const callback = jest.fn(); + + extension.subscribe('testListener', callback); + + expect(extension.listeners['testListener']).toBe(callback); +}); + + +it('should set properties from manifest', async () => { + const origin = 'test-origin'; + const options = {}; + const extension = new Extension(origin, options); + + const mockManifest = { + name: 'test-name', + productName: 'Test Product', + version: '1.0.0', + main: 'index.js', + description: 'Test description' + }; + + jest.mock('pacote', () => ({ + manifest: jest.fn().mockResolvedValue(mockManifest) + })); + + await extension.getManifest(); + + expect(extension.name).toBe('test-name'); + expect(extension.productName).toBe('Test Product'); + expect(extension.version).toBe('1.0.0'); + expect(extension.main).toBe('index.js'); + expect(extension.description).toBe('Test description'); +}); + diff --git a/core/src/node/extension/manager.test.ts b/core/src/node/extension/manager.test.ts new file mode 100644 index 000000000..1c8123d21 --- /dev/null +++ b/core/src/node/extension/manager.test.ts @@ -0,0 +1,28 @@ +import * as fs from 'fs'; +import { join } from 'path'; +import { ExtensionManager } from './manager'; + +it('should throw an error when an invalid path is provided', () => { + const manager = new ExtensionManager(); + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + expect(() => manager.setExtensionsPath('')).toThrow('Invalid path provided to the extensions folder'); +}); + + +it('should return an empty string when extensionsPath is not set', () => { + const manager = new ExtensionManager(); + expect(manager.getExtensionsFile()).toBe(join('', 'extensions.json')); +}); + + +it('should return undefined if no path is set', () => { + const manager = new ExtensionManager(); + expect(manager.getExtensionsPath()).toBeUndefined(); +}); + + +it('should return the singleton instance', () => { + const instance1 = new ExtensionManager(); + const instance2 = new ExtensionManager(); + expect(instance1).toBe(instance2); +}); diff --git a/core/src/node/extension/store.test.ts b/core/src/node/extension/store.test.ts new file mode 100644 index 000000000..cbaa84f7c --- /dev/null +++ b/core/src/node/extension/store.test.ts @@ -0,0 +1,43 @@ +import { getAllExtensions } from './store'; +import { getActiveExtensions } from './store'; +import { getExtension } from './store'; + +test('should return empty array when no extensions added', () => { + expect(getAllExtensions()).toEqual([]); +}); + + +test('should throw error when extension does not exist', () => { + expect(() => getExtension('nonExistentExtension')).toThrow('Extension nonExistentExtension does not exist'); +}); + +import { addExtension } from './store'; +import Extension from './extension'; + +test('should return all extensions when multiple extensions added', () => { + const ext1 = new Extension('ext1'); + ext1.name = 'ext1'; + const ext2 = new Extension('ext2'); + ext2.name = 'ext2'; + + addExtension(ext1, false); + addExtension(ext2, false); + + expect(getAllExtensions()).toEqual([ext1, ext2]); +}); + + + +test('should return only active extensions', () => { + const ext1 = new Extension('ext1'); + ext1.name = 'ext1'; + ext1.setActive(true); + const ext2 = new Extension('ext2'); + ext2.name = 'ext2'; + ext2.setActive(false); + + addExtension(ext1, false); + addExtension(ext2, false); + + expect(getActiveExtensions()).toEqual([ext1]); +}); diff --git a/core/src/node/helper/config.test.ts b/core/src/node/helper/config.test.ts new file mode 100644 index 000000000..201a98141 --- /dev/null +++ b/core/src/node/helper/config.test.ts @@ -0,0 +1,14 @@ +import { getEngineConfiguration } from './config'; +import { getAppConfigurations, defaultAppConfig } from './config'; + +it('should return undefined for invalid engine ID', async () => { + const config = await getEngineConfiguration('invalid_engine'); + expect(config).toBeUndefined(); +}); + + +it('should return default config when CI is e2e', () => { + process.env.CI = 'e2e'; + const config = getAppConfigurations(); + expect(config).toEqual(defaultAppConfig()); +}); diff --git a/core/src/node/helper/download.test.ts b/core/src/node/helper/download.test.ts new file mode 100644 index 000000000..95cc553b5 --- /dev/null +++ b/core/src/node/helper/download.test.ts @@ -0,0 +1,11 @@ +import { DownloadManager } from './download'; + +it('should set a network request for a specific file', () => { + const downloadManager = new DownloadManager(); + const fileName = 'testFile'; + const request = { url: 'http://example.com' }; + + downloadManager.setRequest(fileName, request); + + expect(downloadManager.networkRequests[fileName]).toEqual(request); +}); diff --git a/core/src/node/helper/logger.test.ts b/core/src/node/helper/logger.test.ts new file mode 100644 index 000000000..0f44bfcd4 --- /dev/null +++ b/core/src/node/helper/logger.test.ts @@ -0,0 +1,47 @@ +import { Logger, LoggerManager } from './logger'; + + it('should flush queued logs to registered loggers', () => { + class TestLogger extends Logger { + name = 'testLogger'; + log(args: any): void { + console.log(args); + } + } + const loggerManager = new LoggerManager(); + const testLogger = new TestLogger(); + loggerManager.register(testLogger); + const logSpy = jest.spyOn(testLogger, 'log'); + loggerManager.log('test log'); + expect(logSpy).toHaveBeenCalledWith('test log'); + }); + + + it('should unregister a logger', () => { + class TestLogger extends Logger { + name = 'testLogger'; + log(args: any): void { + console.log(args); + } + } + const loggerManager = new LoggerManager(); + const testLogger = new TestLogger(); + loggerManager.register(testLogger); + loggerManager.unregister('testLogger'); + const retrievedLogger = loggerManager.get('testLogger'); + expect(retrievedLogger).toBeUndefined(); + }); + + + it('should register and retrieve a logger', () => { + class TestLogger extends Logger { + name = 'testLogger'; + log(args: any): void { + console.log(args); + } + } + const loggerManager = new LoggerManager(); + const testLogger = new TestLogger(); + loggerManager.register(testLogger); + const retrievedLogger = loggerManager.get('testLogger'); + expect(retrievedLogger).toBe(testLogger); + }); diff --git a/core/src/node/helper/module.test.ts b/core/src/node/helper/module.test.ts new file mode 100644 index 000000000..bb8327cbf --- /dev/null +++ b/core/src/node/helper/module.test.ts @@ -0,0 +1,23 @@ +import { ModuleManager } from './module'; + +it('should clear all imported modules', () => { + const moduleManager = new ModuleManager(); + moduleManager.setModule('module1', { key: 'value1' }); + moduleManager.setModule('module2', { key: 'value2' }); + moduleManager.clearImportedModules(); + expect(moduleManager.requiredModules).toEqual({}); +}); + + +it('should set a module correctly', () => { + const moduleManager = new ModuleManager(); + moduleManager.setModule('testModule', { key: 'value' }); + expect(moduleManager.requiredModules['testModule']).toEqual({ key: 'value' }); +}); + + +it('should return the singleton instance', () => { + const instance1 = new ModuleManager(); + const instance2 = new ModuleManager(); + expect(instance1).toBe(instance2); +}); diff --git a/core/src/node/helper/path.test.ts b/core/src/node/helper/path.test.ts new file mode 100644 index 000000000..f9a3b5766 --- /dev/null +++ b/core/src/node/helper/path.test.ts @@ -0,0 +1,29 @@ +import { normalizeFilePath } from './path' + +import { jest } from '@jest/globals' +describe('Test file normalize', () => { + test('returns no file protocol prefix on Unix', async () => { + expect(normalizeFilePath('file://test.txt')).toBe('test.txt') + expect(normalizeFilePath('file:/test.txt')).toBe('test.txt') + }) + test('returns no file protocol prefix on Windows', async () => { + expect(normalizeFilePath('file:\\\\test.txt')).toBe('test.txt') + expect(normalizeFilePath('file:\\test.txt')).toBe('test.txt') + }) + + test('returns correct path when Electron is available and app is not packaged', () => { + const electronMock = { + app: { + getAppPath: jest.fn().mockReturnValue('/mocked/path'), + isPackaged: false, + }, + protocol: {}, + } + jest.mock('electron', () => electronMock) + + const { appResourcePath } = require('./path') + + const expectedPath = process.platform === 'win32' ? '\\mocked\\path' : '/mocked/path' + expect(appResourcePath()).toBe(expectedPath) + }) +}) diff --git a/core/src/node/helper/resource.test.ts b/core/src/node/helper/resource.test.ts new file mode 100644 index 000000000..aaeab9d65 --- /dev/null +++ b/core/src/node/helper/resource.test.ts @@ -0,0 +1,15 @@ +import { getSystemResourceInfo } from './resource'; + +it('should return the correct system resource information with a valid CPU count', async () => { + const mockCpuCount = 4; + jest.spyOn(require('./config'), 'physicalCpuCount').mockResolvedValue(mockCpuCount); + const logSpy = jest.spyOn(require('./logger'), 'log').mockImplementation(() => {}); + + const result = await getSystemResourceInfo(); + + expect(result).toEqual({ + numCpuPhysicalCore: mockCpuCount, + memAvailable: 0, + }); + expect(logSpy).toHaveBeenCalledWith(`[CORTEX]::CPU information - ${mockCpuCount}`); +}); diff --git a/core/src/types/assistant/assistantEvent.test.ts b/core/src/types/assistant/assistantEvent.test.ts new file mode 100644 index 000000000..4b1ed552c --- /dev/null +++ b/core/src/types/assistant/assistantEvent.test.ts @@ -0,0 +1,7 @@ +import { AssistantEvent } from './assistantEvent'; +it('dummy test', () => { expect(true).toBe(true); }); + +it('should contain OnAssistantsUpdate event', () => { + expect(AssistantEvent.OnAssistantsUpdate).toBe('OnAssistantsUpdate'); +}); + diff --git a/core/src/types/file/index.ts b/core/src/types/file/index.ts index d941987ef..1b36a5777 100644 --- a/core/src/types/file/index.ts +++ b/core/src/types/file/index.ts @@ -16,7 +16,7 @@ export type DownloadState = { error?: string extensionId?: string - downloadType?: DownloadType + downloadType?: DownloadType | string localPath?: string } @@ -40,7 +40,7 @@ export type DownloadRequest = { */ extensionId?: string - downloadType?: DownloadType + downloadType?: DownloadType | string } type DownloadTime = { diff --git a/core/testRunner.js b/core/testRunner.js new file mode 100644 index 000000000..b0d108160 --- /dev/null +++ b/core/testRunner.js @@ -0,0 +1,10 @@ +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/tests/node/path.test.ts b/core/tests/node/path.test.ts deleted file mode 100644 index 5390df119..000000000 --- a/core/tests/node/path.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { normalizeFilePath } from "../../src/node/helper/path"; - -describe("Test file normalize", () => { - test("returns no file protocol prefix on Unix", async () => { - expect(normalizeFilePath("file://test.txt")).toBe("test.txt"); - expect(normalizeFilePath("file:/test.txt")).toBe("test.txt"); - }); - test("returns no file protocol prefix on Windows", async () => { - expect(normalizeFilePath("file:\\\\test.txt")).toBe("test.txt"); - expect(normalizeFilePath("file:\\test.txt")).toBe("test.txt"); - }); -}); diff --git a/core/tsconfig.json b/core/tsconfig.json index daeb7eeff..02caf65e2 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -16,4 +16,5 @@ "types": ["@types/jest"], }, "include": ["src"], + "exclude": ["**/*.test.ts"] } diff --git a/jest.config.js b/jest.config.js index eb1aab657..a911a7f0a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,3 @@ module.exports = { - projects: ['/core', '/web'], + projects: ['/core', '/web', '/joi'], } diff --git a/web/utils/Stack.test.ts b/web/utils/Stack.test.ts new file mode 100644 index 000000000..e10753c68 --- /dev/null +++ b/web/utils/Stack.test.ts @@ -0,0 +1,35 @@ + +import { Stack } from './Stack'; + +it('should return elements in reverse order', () => { + const stack = new Stack(); + stack.push(1); + stack.push(2); + stack.push(3); + const reversedOutput = stack.reverseOutput(); + expect(reversedOutput).toEqual([1, 2, 3]); +}); + + +it('should pop an element from the stack', () => { + const stack = new Stack(); + stack.push(1); + const poppedElement = stack.pop(); + expect(poppedElement).toBe(1); + expect(stack.isEmpty()).toBe(true); +}); + + +it('should push an element to the stack', () => { + const stack = new Stack(); + stack.push(1); + expect(stack.isEmpty()).toBe(false); + expect(stack.size()).toBe(1); + expect(stack.peek()).toBe(1); +}); + + +it('should initialize as empty', () => { + const stack = new Stack(); + expect(stack.isEmpty()).toBe(true); +}); diff --git a/web/utils/componentSettings.test.ts b/web/utils/componentSettings.test.ts new file mode 100644 index 000000000..51d2a90fd --- /dev/null +++ b/web/utils/componentSettings.test.ts @@ -0,0 +1,22 @@ + +import { getConfigurationsData } from './componentSettings'; + +it('should process checkbox setting', () => { + const settings = { embedding: true }; + const result = getConfigurationsData(settings); + expect(result[0].controllerProps.value).toBe(true); +}); + + +it('should process input setting and handle array input', () => { + const settings = { prompt_template: ['Hello', 'World', ''] }; + const result = getConfigurationsData(settings); + expect(result[0].controllerProps.value).toBe('Hello World '); +}); + + +it('should return an empty array when settings object is empty', () => { + const settings = {}; + const result = getConfigurationsData(settings); + expect(result).toEqual([]); +}); diff --git a/web/utils/datetime.test.ts b/web/utils/datetime.test.ts new file mode 100644 index 000000000..75bdb1f8f --- /dev/null +++ b/web/utils/datetime.test.ts @@ -0,0 +1,27 @@ + +import { displayDate } from './datetime'; +import { isToday } from './datetime'; + +test('should return only time for today\'s timestamp', () => { + const today = new Date(); + const timestamp = today.getTime(); + const expectedTime = today.toLocaleTimeString(undefined, { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: true, + }); + expect(displayDate(timestamp)).toBe(expectedTime); +}); + + +test('should return N/A for undefined timestamp', () => { + expect(displayDate()).toBe('N/A'); +}); + + +test('should return true for today\'s timestamp', () => { + const today = new Date(); + const timestamp = today.setHours(0, 0, 0, 0); + expect(isToday(timestamp)).toBe(true); +}); diff --git a/web/utils/jsonToCssVariables.test.ts b/web/utils/jsonToCssVariables.test.ts new file mode 100644 index 000000000..0ff19e9a2 --- /dev/null +++ b/web/utils/jsonToCssVariables.test.ts @@ -0,0 +1,17 @@ + +import cssVars from './jsonToCssVariables'; + +test('should convert nested JSON object to CSS variables', () => { + const input = { theme: { color: 'blue', font: { size: '14px', weight: 'bold' } } }; + const expectedOutput = '--theme-color: blue;--theme-font-size: 14px;--theme-font-weight: bold;'; + const result = cssVars(input); + expect(result).toBe(expectedOutput); +}); + + +test('should convert simple JSON object to CSS variables', () => { + const input = { color: 'red', fontSize: '16px' }; + const expectedOutput = '--color: red;--fontSize: 16px;'; + const result = cssVars(input); + expect(result).toBe(expectedOutput); +}); diff --git a/web/utils/memory.test.ts b/web/utils/memory.test.ts index e7420957d..5150df72b 100644 --- a/web/utils/memory.test.ts +++ b/web/utils/memory.test.ts @@ -1,5 +1,3 @@ -// @auto-generated - import { utilizedMemory } from './memory' test('test_utilizedMemory_arbitraryValues', () => { diff --git a/web/utils/predefinedComponent.test.ts b/web/utils/predefinedComponent.test.ts new file mode 100644 index 000000000..42b391d73 --- /dev/null +++ b/web/utils/predefinedComponent.test.ts @@ -0,0 +1,18 @@ + +import { presetConfiguration } from './predefinedComponent'; + +it('should have correct configuration for prompt_template', () => { + const config = presetConfiguration['prompt_template']; + expect(config).toEqual({ + key: 'prompt_template', + title: 'Prompt template', + description: `A predefined text or framework that guides the AI model's response generation. It includes placeholders or instructions for the model to fill in or expand upon.`, + controllerType: 'input', + controllerProps: { + placeholder: 'Prompt template', + value: '', + }, + requireModelReload: true, + configType: 'setting', + }); +}); diff --git a/web/utils/thread.test.ts b/web/utils/thread.test.ts new file mode 100644 index 000000000..672e3adde --- /dev/null +++ b/web/utils/thread.test.ts @@ -0,0 +1,9 @@ + +import { generateThreadId } from './thread'; + +test('shouldGenerateThreadIdWithCorrectFormat', () => { + const assistantId = 'assistant123'; + const threadId = generateThreadId(assistantId); + const regex = /^assistant123_\d{10}$/; + expect(threadId).toMatch(regex); +}); diff --git a/web/utils/titleUtils.test.ts b/web/utils/titleUtils.test.ts new file mode 100644 index 000000000..0cf2e1db7 --- /dev/null +++ b/web/utils/titleUtils.test.ts @@ -0,0 +1,25 @@ + +import { openFileTitle } from './titleUtils'; + + test('should return "Open Containing Folder" when neither isMac nor isWindows is true', () => { + (global as any).isMac = false; + (global as any).isWindows = false; + const result = openFileTitle(); + expect(result).toBe('Open Containing Folder'); + }); + + + test('should return "Show in File Explorer" when isWindows is true', () => { + (global as any).isMac = false; + (global as any).isWindows = true; + const result = openFileTitle(); + expect(result).toBe('Show in File Explorer'); + }); + + + test('should return "Show in Finder" when isMac is true', () => { + (global as any).isMac = true; + (global as any).isWindows = false; + const result = openFileTitle(); + expect(result).toBe('Show in Finder'); + });