diff --git a/.gitignore b/.gitignore
index f9b1dab66..646e6842a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,5 @@ electron/test-data
electron/test-results
core/test_results.html
coverage
+.yarn
+.yarnrc
diff --git a/electron/.eslintrc.js b/electron/.eslintrc.js
index d252ec42b..a8b7c00cb 100644
--- a/electron/.eslintrc.js
+++ b/electron/.eslintrc.js
@@ -34,5 +34,5 @@ module.exports = {
{ name: 'Link', linkAttribute: 'to' },
],
},
- ignorePatterns: ['build', 'renderer', 'node_modules', '@global'],
+ ignorePatterns: ['build', 'renderer', 'node_modules', '@global', 'playwright-report'],
}
diff --git a/web/.eslintrc.js b/web/.eslintrc.js
index 73fb1bcf5..2b72273b6 100644
--- a/web/.eslintrc.js
+++ b/web/.eslintrc.js
@@ -14,6 +14,7 @@ module.exports = {
'**/*.test.tsx',
'**/*.test.ts',
'testRunner.js',
+ 'jest.config.js',
],
extends: [
'next/core-web-vitals',
diff --git a/web/containers/SliderRightPanel/index.test.tsx b/web/containers/SliderRightPanel/index.test.tsx
new file mode 100644
index 000000000..4274f0fd5
--- /dev/null
+++ b/web/containers/SliderRightPanel/index.test.tsx
@@ -0,0 +1,92 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { fireEvent } from '@testing-library/dom'
+import SliderRightPanel from './index'
+import '@testing-library/jest-dom'
+
+class ResizeObserverMock {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+}
+
+global.ResizeObserver = ResizeObserverMock
+
+jest.mock('@janhq/joi', () => ({
+ ...jest.requireActual('@janhq/joi'),
+ Slider: ({ children, onValueChange, ...props }: any) => (
+
+
+ onValueChange && onValueChange([parseInt(e.target.value)])
+ }
+ />
+ {children}
+
+ ),
+}))
+
+describe('SliderRightPanel', () => {
+ const defaultProps = {
+ title: 'Test Slider',
+ disabled: false,
+ min: 0,
+ max: 100,
+ step: 1,
+ description: 'This is a test slider',
+ value: 50,
+ onValueChanged: jest.fn(),
+ }
+
+ it('renders correctly with given props', () => {
+ const { getByText } = render()
+ expect(getByText('Test Slider')).toBeInTheDocument()
+ })
+
+ it('calls onValueChanged with correct value when input is changed', () => {
+ defaultProps.onValueChanged = jest.fn()
+ const { getByRole } = render()
+
+ const input = getByRole('textbox')
+ fireEvent.change(input, { target: { value: '75' } })
+ expect(defaultProps.onValueChanged).toHaveBeenCalledWith(75)
+ })
+
+ it('calls onValueChanged with correct value when slider is changed', () => {
+ defaultProps.onValueChanged = jest.fn()
+ const { getByTestId } = render()
+
+ const input = getByTestId('slider-input')
+ fireEvent.change(input, { target: { value: '75' } })
+ expect(defaultProps.onValueChanged).toHaveBeenCalledWith(75)
+ })
+
+ it('calls onValueChanged with max value when input exceeds max', () => {
+ defaultProps.onValueChanged = jest.fn()
+ const { getByRole } = render()
+ const input = getByRole('textbox')
+ fireEvent.change(input, { target: { value: '150' } })
+ fireEvent.focusOut(input)
+ expect(defaultProps.onValueChanged).toHaveBeenCalledWith(100)
+ })
+
+ it('calls onValueChanged with min value when input is below min', () => {
+ defaultProps.onValueChanged = jest.fn()
+ const { getByRole } = render()
+ const input = getByRole('textbox')
+ fireEvent.change(input, { target: { value: '0' } })
+ fireEvent.focusOut(input)
+ expect(defaultProps.onValueChanged).toHaveBeenCalledWith(0)
+ })
+
+ it('does not call onValueChanged when input is invalid', () => {
+ defaultProps.onValueChanged = jest.fn()
+ const { getByRole } = render()
+ const input = getByRole('textbox')
+ fireEvent.change(input, { target: { value: 'invalid' } })
+ expect(defaultProps.onValueChanged).not.toHaveBeenCalledWith(0)
+ })
+})
diff --git a/web/containers/SliderRightPanel/index.tsx b/web/containers/SliderRightPanel/index.tsx
index af7569860..c9f75fdc9 100644
--- a/web/containers/SliderRightPanel/index.tsx
+++ b/web/containers/SliderRightPanel/index.tsx
@@ -6,7 +6,7 @@ import { useClickOutside } from '@janhq/joi'
import { InfoIcon } from 'lucide-react'
type Props = {
- name: string
+ name?: string
title: string
disabled: boolean
description: string
@@ -87,7 +87,17 @@ const SliderRightPanel = ({
}
}}
onChange={(e) => {
+ // Should not accept invalid value or NaN
+ // E.g. anything changes that trigger onValueChanged
+ // Which is incorrect
+ if (Number(e.target.value) > Number(max)) {
+ setVal(max.toString())
+ } else if (Number(e.target.value) < Number(min)) {
+ setVal(min.toString())
+ } else if (Number.isNaN(Number(e.target.value))) return
+
onValueChanged?.(Number(e.target.value))
+ // TODO: How to support negative number input?
if (/^\d*\.?\d*$/.test(e.target.value)) {
setVal(e.target.value)
}
diff --git a/web/hooks/useCreateNewThread.ts b/web/hooks/useCreateNewThread.ts
index 2e4760051..80acfa3cc 100644
--- a/web/hooks/useCreateNewThread.ts
+++ b/web/hooks/useCreateNewThread.ts
@@ -135,9 +135,8 @@ export const useCreateNewThread = () => {
tools: experimentalEnabled ? [assistantTools] : assistant.tools,
model: {
id: defaultModel?.id ?? '*',
- settings: { ...defaultModel?.settings, ...overriddenSettings } ?? {},
- parameters:
- { ...defaultModel?.parameters, ...overriddenParameters } ?? {},
+ settings: { ...defaultModel?.settings, ...overriddenSettings },
+ parameters: { ...defaultModel?.parameters, ...overriddenParameters },
engine: defaultModel?.engine,
},
instructions,
diff --git a/web/jest.config.js b/web/jest.config.js
index b34e410d2..bbdf21742 100644
--- a/web/jest.config.js
+++ b/web/jest.config.js
@@ -1,5 +1,15 @@
-module.exports = {
- preset: 'ts-jest',
- testEnvironment: 'node',
- runner: './testRunner.js',
+const nextJest = require('next/jest')
+
+/** @type {import('jest').Config} */
+const createJestConfig = nextJest({})
+
+// Add any custom config to be passed to Jest
+const config = {
+ coverageProvider: 'v8',
+ testEnvironment: 'jsdom',
+ // Add more setup options before each test is run
+ // setupFilesAfterEnv: ['/jest.setup.ts'],
}
+
+// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
+module.exports = createJestConfig(config)
diff --git a/web/package.json b/web/package.json
index 3ba02c3b9..5a8b3ea34 100644
--- a/web/package.json
+++ b/web/package.json
@@ -54,6 +54,7 @@
},
"devDependencies": {
"@next/eslint-plugin-next": "^14.0.1",
+ "@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.14.200",
"@types/node": "20.8.10",
@@ -74,6 +75,7 @@
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
+ "jest-environment-jsdom": "^29.7.0",
"jest-runner": "^29.7.0",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.6",
diff --git a/web/tsconfig.json b/web/tsconfig.json
index caa244645..429d01a0a 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -5,7 +5,8 @@
"typeRoots": [
"./node_modules/@types",
"./src/types",
- "../node_modules/@types/jest"
+ "../node_modules/@types/jest",
+ "@testing-library/jest-dom"
],
"allowJs": true,
"skipLibCheck": true,
@@ -29,5 +30,5 @@
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules", "**/*.test.ts"]
+ "exclude": ["node_modules"]
}