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"] }