+
+ )
+
+ // Check that the container renders with no border or rounded corners
+ const container = screen.getByText('Test Child').parentElement
+ expect(container).not.toHaveClass('rounded-lg border')
+ })
+
+ it('renders with reduceTransparent set to false', () => {
+ // Mock reduceTransparentAtom to be false
+ ;(useAtomValue as jest.Mock).mockReturnValue(false)
+
+ render(
+
+
Test Child
+
+ )
+
+ // Check that the container renders with border and rounded corners
+ const container = screen.getByText('Test Child').parentElement
+ expect(container).toHaveClass('rounded-lg border')
+ })
+
+ it('renders children correctly', () => {
+ // Mock reduceTransparentAtom to be true for this test
+ ;(useAtomValue as jest.Mock).mockReturnValue(true)
+
+ render(
+
+
Child Content
+
+ )
+
+ // Verify that the child content is rendered
+ expect(screen.getByText('Child Content')).toBeInTheDocument()
+ })
+})
diff --git a/web/containers/CopyInstruction/index.test.tsx b/web/containers/CopyInstruction/index.test.tsx
new file mode 100644
index 0000000000..2f00e4e37c
--- /dev/null
+++ b/web/containers/CopyInstruction/index.test.tsx
@@ -0,0 +1,65 @@
+import { render, screen, fireEvent } from '@testing-library/react'
+import { useAtom } from 'jotai'
+import '@testing-library/jest-dom'
+import CopyOverInstruction from './index'
+
+// Mock the `useAtom` hook from jotai
+jest.mock('jotai', () => ({
+ useAtom: jest.fn(),
+}))
+
+describe('CopyOverInstruction', () => {
+ const setCopyOverInstructionEnabled = jest.fn()
+
+ beforeEach(() => {
+ ;(useAtom as jest.Mock).mockImplementation(() => [
+ false,
+ setCopyOverInstructionEnabled,
+ ])
+ })
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('should render the component with the switch in the correct state', () => {
+ render()
+
+ // Assert the text is rendered
+ expect(
+ screen.getByText(/Save instructions for new threads/i)
+ ).toBeInTheDocument()
+
+ // Assert the switch is rendered and in the unchecked state
+ const switchInput = screen.getByRole('checkbox')
+ expect(switchInput).toBeInTheDocument()
+ expect(switchInput).not.toBeChecked()
+ })
+
+ it('should call setCopyOverInstructionEnabled when the switch is toggled', () => {
+ render()
+
+ const switchInput = screen.getByRole('checkbox')
+
+ // Simulate toggling the switch
+ fireEvent.click(switchInput)
+
+ // Assert that the atom setter is called with true when checked
+ expect(setCopyOverInstructionEnabled).toHaveBeenCalledWith(true)
+ })
+
+ it('should reflect the updated state when the atom value changes', () => {
+ // Mock the atom to return true (enabled state)
+ ;(useAtom as jest.Mock).mockImplementation(() => [
+ true,
+ setCopyOverInstructionEnabled,
+ ])
+
+ render()
+
+ const switchInput = screen.getByRole('checkbox')
+
+ // The switch should now be checked
+ expect(switchInput).toBeChecked()
+ })
+})
diff --git a/web/containers/EngineSetting/index.test.tsx b/web/containers/EngineSetting/index.test.tsx
new file mode 100644
index 0000000000..140a36395a
--- /dev/null
+++ b/web/containers/EngineSetting/index.test.tsx
@@ -0,0 +1,115 @@
+import { render } from '@testing-library/react'
+import '@testing-library/jest-dom'
+import EngineSetting from './index'
+import SettingComponentBuilder from '@/containers/ModelSetting/SettingComponent'
+import { SettingComponentProps } from '@janhq/core'
+
+// Mock the SettingComponentBuilder component
+jest.mock('@/containers/ModelSetting/SettingComponent', () =>
+ jest.fn(() => null)
+)
+
+describe('EngineSetting', () => {
+ const mockComponentData: SettingComponentProps[] = [
+ {
+ key: 'setting1',
+ title: 'Setting 1',
+ description: 'This is the first setting.',
+ controllerType: 'input',
+ controllerProps: {
+ placeholder: 'Enter text',
+ value: 'default text',
+ type: 'text',
+ },
+ },
+ {
+ key: 'setting2',
+ title: 'Setting 2',
+ description: 'This is the second setting.',
+ controllerType: 'slider',
+ controllerProps: {
+ min: 0,
+ max: 100,
+ step: 1,
+ value: 50,
+ },
+ },
+ {
+ key: 'setting3',
+ title: 'Setting 3',
+ description: 'This is the third setting.',
+ controllerType: 'checkbox',
+ controllerProps: {
+ value: true,
+ },
+ },
+ ]
+
+ const onValueChangedMock = jest.fn()
+
+ afterEach(() => {
+ jest.clearAllMocks() // Clear mocks after each test
+ })
+
+ it('renders SettingComponentBuilder with the correct props', () => {
+ render(
+
+ )
+
+ // Check that SettingComponentBuilder is called with the correct props
+ expect(SettingComponentBuilder).toHaveBeenCalledWith(
+ {
+ componentProps: mockComponentData,
+ disabled: false,
+ onValueUpdated: onValueChangedMock,
+ },
+ {}
+ )
+ })
+
+ it('renders SettingComponentBuilder with disabled prop', () => {
+ render(
+
+ )
+
+ // Check that SettingComponentBuilder is called with disabled=true
+ expect(SettingComponentBuilder).toHaveBeenCalledWith(
+ {
+ componentProps: mockComponentData,
+ disabled: true,
+ onValueUpdated: onValueChangedMock,
+ },
+ {}
+ )
+ })
+
+ it('calls onValueChanged when the value is updated', () => {
+ // Simulating value update in SettingComponentBuilder
+ ;(SettingComponentBuilder as jest.Mock).mockImplementation(
+ ({ onValueUpdated }) => {
+ // Simulate calling the value update handler
+ onValueUpdated('setting1', 'new value')
+ return null
+ }
+ )
+
+ render(
+
+ )
+
+ // Assert that onValueChanged is called with the correct parameters
+ expect(onValueChangedMock).toHaveBeenCalledWith('setting1', 'new value')
+ })
+})
diff --git a/web/containers/Loader/Bubble.tsx b/web/containers/Loader/Bubble.tsx
deleted file mode 100644
index 0cd438ce0c..0000000000
--- a/web/containers/Loader/Bubble.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-export default function BubbleLoader() {
- return (
-
-
-
-
-
- )
-}
diff --git a/web/containers/Loader/ModelStart.test.tsx b/web/containers/Loader/ModelStart.test.tsx
new file mode 100644
index 0000000000..62e333da05
--- /dev/null
+++ b/web/containers/Loader/ModelStart.test.tsx
@@ -0,0 +1,47 @@
+import '@testing-library/jest-dom'
+import { render, screen, act } from '@testing-library/react'
+import ModelStart from './ModelStart' // Adjust the path based on your file structure
+import { useActiveModel } from '@/hooks/useActiveModel'
+
+// Mock the useActiveModel hook
+jest.mock('@/hooks/useActiveModel', () => ({
+ useActiveModel: jest.fn(),
+}))
+
+describe('ModelStart', () => {
+ const mockSetStateModel = jest.fn()
+ const mockModel = { id: 'test-model' }
+
+ beforeEach(() => {
+ // Reset the mock implementation before each test
+ jest.clearAllMocks()
+ })
+
+ it('renders correctly when loading is false', () => {
+ ;(useActiveModel as jest.Mock).mockReturnValue({
+ stateModel: {
+ loading: false,
+ state: 'start',
+ model: mockModel,
+ },
+ })
+
+ render()
+ // Ensure the component returns null when not loading
+ expect(screen.queryByText(/Starting model/i)).toBeNull()
+ })
+
+ it('renders loading state with model id', () => {
+ ;(useActiveModel as jest.Mock).mockReturnValue({
+ stateModel: {
+ loading: true,
+ state: 'start',
+ model: mockModel,
+ },
+ })
+
+ render()
+ // Ensure the loading text is rendered
+ expect(screen.getByText(/Starting model test-model/i)).toBeInTheDocument()
+ })
+})
diff --git a/web/containers/LoadingModal/index.test.tsx b/web/containers/LoadingModal/index.test.tsx
new file mode 100644
index 0000000000..f5b17234d8
--- /dev/null
+++ b/web/containers/LoadingModal/index.test.tsx
@@ -0,0 +1,47 @@
+import '@testing-library/jest-dom'
+import { render } from '@testing-library/react'
+import { useAtomValue } from 'jotai'
+import ResettingModal from './index'
+
+// Mocking the Jotai atom
+jest.mock('jotai', () => {
+ const originalModule = jest.requireActual('jotai')
+
+ return {
+ ...originalModule,
+ useAtomValue: jest.fn(),
+ }
+})
+
+describe('ResettingModal', () => {
+ it('renders the modal with loading info when provided', () => {
+ const mockLoadingInfo = {
+ title: 'Loading...',
+ message: 'Please wait while we process your request.',
+ }
+
+ // Mock the useAtomValue hook to return mock loading info
+ ;(useAtomValue as jest.Mock).mockReturnValue(mockLoadingInfo)
+
+ const { getByText } = render()
+
+ // Check if the modal title and message are displayed
+ expect(getByText('Loading...')).toBeInTheDocument()
+ expect(
+ getByText('Please wait while we process your request.')
+ ).toBeInTheDocument()
+ })
+
+ it('does not render the modal when loading info is undefined', () => {
+ // Mock the useAtomValue hook to return undefined
+ ;(useAtomValue as jest.Mock).mockReturnValue(undefined)
+
+ const { queryByText } = render()
+
+ // Check that the modal does not appear
+ expect(queryByText('Loading...')).not.toBeInTheDocument()
+ expect(
+ queryByText('Please wait while we process your request.')
+ ).not.toBeInTheDocument()
+ })
+})
diff --git a/web/containers/MainViewContainer/index.test.tsx b/web/containers/MainViewContainer/index.test.tsx
new file mode 100644
index 0000000000..bcafa92afb
--- /dev/null
+++ b/web/containers/MainViewContainer/index.test.tsx
@@ -0,0 +1,56 @@
+import '@testing-library/jest-dom'
+
+import { render } from '@testing-library/react'
+import { useAtomValue } from 'jotai'
+import MainViewContainer from './index'
+import { MainViewState } from '@/constants/screens'
+
+// Mocking the Jotai atom
+jest.mock('jotai', () => {
+ const originalModule = jest.requireActual('jotai')
+
+ return {
+ ...originalModule,
+ useAtomValue: jest.fn(),
+ }
+})
+
+// Mocking the screen components
+jest.mock('@/screens/Hub', () => () =>
Hub Screen
)
+jest.mock('@/screens/LocalServer', () => () =>
Local Server Screen
)
+jest.mock('@/screens/Settings', () => () =>
Settings Screen
)
+jest.mock('@/screens/Thread', () => () =>
Thread Screen
)
+
+describe('MainViewContainer', () => {
+ it('renders HubScreen when mainViewState is Hub', () => {
+ ;(useAtomValue as jest.Mock).mockReturnValue(MainViewState.Hub)
+
+ const { getByText } = render()
+
+ expect(getByText('Hub Screen')).toBeInTheDocument()
+ })
+
+ it('renders SettingsScreen when mainViewState is Settings', () => {
+ ;(useAtomValue as jest.Mock).mockReturnValue(MainViewState.Settings)
+
+ const { getByText } = render()
+
+ expect(getByText('Settings Screen')).toBeInTheDocument()
+ })
+
+ it('renders LocalServerScreen when mainViewState is LocalServer', () => {
+ ;(useAtomValue as jest.Mock).mockReturnValue(MainViewState.LocalServer)
+
+ const { getByText } = render()
+
+ expect(getByText('Local Server Screen')).toBeInTheDocument()
+ })
+
+ it('renders ThreadScreen when mainViewState is not defined', () => {
+ ;(useAtomValue as jest.Mock).mockReturnValue(undefined)
+
+ const { getByText } = render()
+
+ expect(getByText('Thread Screen')).toBeInTheDocument()
+ })
+})
diff --git a/web/containers/ModelConfigInput/index.test.tsx b/web/containers/ModelConfigInput/index.test.tsx
new file mode 100644
index 0000000000..b92bdfcb23
--- /dev/null
+++ b/web/containers/ModelConfigInput/index.test.tsx
@@ -0,0 +1,85 @@
+import '@testing-library/jest-dom'
+import React from 'react'
+import { render, fireEvent } from '@testing-library/react'
+import ModelConfigInput from './index'
+import { Tooltip } from '@janhq/joi'
+
+// Mocking the Tooltip component to simplify testing
+jest.mock('@janhq/joi', () => ({
+ ...jest.requireActual('@janhq/joi'),
+ Tooltip: ({
+ trigger,
+ content,
+ }: {
+ trigger: React.ReactNode
+ content: string
+ }) => (
+
+ {trigger}
+ {content}
+
+ ),
+}))
+
+describe('ModelConfigInput', () => {
+ it('renders correctly with given props', () => {
+ const { getByText, getByPlaceholderText } = render(
+
+ )
+
+ // Check if title is rendered
+ expect(getByText('Test Title')).toBeInTheDocument()
+
+ // Check if the description tooltip content is rendered
+ expect(getByText('This is a description.')).toBeInTheDocument()
+
+ // Check if the placeholder is rendered
+ expect(getByPlaceholderText('Enter text here')).toBeInTheDocument()
+ })
+
+ it('calls onValueChanged when value changes', () => {
+ const onValueChangedMock = jest.fn()
+ const { getByPlaceholderText } = render(
+
+ )
+
+ const textArea = getByPlaceholderText('Enter text here')
+
+ // Simulate typing in the textarea
+ fireEvent.change(textArea, { target: { value: 'New Value' } })
+
+ // Check if onValueChanged was called with the new value
+ expect(onValueChangedMock).toHaveBeenCalledWith('New Value')
+ })
+
+ it('disables the textarea when disabled prop is true', () => {
+ const { getByPlaceholderText } = render(
+
+ )
+
+ const textArea = getByPlaceholderText('Enter text here')
+
+ // Check if the textarea is disabled
+ expect(textArea).toBeDisabled()
+ })
+})
diff --git a/web/containers/Providers/Responsive.test.tsx b/web/containers/Providers/Responsive.test.tsx
new file mode 100644
index 0000000000..e72a5e7e62
--- /dev/null
+++ b/web/containers/Providers/Responsive.test.tsx
@@ -0,0 +1,110 @@
+import '@testing-library/jest-dom'
+import React from 'react'
+import { render } from '@testing-library/react'
+import { useAtom } from 'jotai'
+import Responsive from './Responsive'
+import { showLeftPanelAtom, showRightPanelAtom } from '@/helpers/atoms/App.atom'
+
+// Mocking the required atoms
+jest.mock('jotai', () => {
+ const originalModule = jest.requireActual('jotai')
+ return {
+ ...originalModule,
+ useAtom: jest.fn(),
+ useAtomValue: jest.fn(),
+ }
+})
+
+const mockSetShowLeftPanel = jest.fn()
+const mockSetShowRightPanel = jest.fn()
+const mockShowLeftPanel = true
+const mockShowRightPanel = true
+
+beforeEach(() => {
+ // Mocking the atom behavior
+ ;(useAtom as jest.Mock).mockImplementation((atom) => {
+ if (atom === showLeftPanelAtom) {
+ return [mockShowLeftPanel, mockSetShowLeftPanel]
+ }
+ if (atom === showRightPanelAtom) {
+ return [mockShowRightPanel, mockSetShowRightPanel]
+ }
+ return [null, jest.fn()]
+ })
+})
+
+describe('Responsive', () => {
+ beforeAll(() => {
+ // Mocking the window.matchMedia function
+ window.matchMedia = jest.fn().mockImplementation((query) => {
+ return {
+ matches: false, // Set this to true to simulate mobile view
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ }
+ })
+ })
+
+ it('renders children correctly', () => {
+ const { getByText } = render(
+
+
Child Content
+
+ )
+
+ // Check if the child content is rendered
+ expect(getByText('Child Content')).toBeInTheDocument()
+ })
+
+ it('hides left and right panels on small screens', () => {
+ // Simulate mobile view
+ window.matchMedia = jest.fn().mockImplementation((query) => ({
+ matches: true, // Change to true to simulate mobile
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ }))
+
+ render(
+
+
Child Content
+
+ )
+
+ // Check that the left and right panel states were updated to false
+ expect(mockSetShowLeftPanel).toHaveBeenCalledWith(false)
+ expect(mockSetShowRightPanel).toHaveBeenCalledWith(false)
+ })
+
+ it('restores the last known panel states on larger screens', () => {
+ // Simulate mobile view first
+ window.matchMedia = jest.fn().mockImplementation((query) => ({
+ matches: true, // Change to true to simulate mobile
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ }))
+
+ render(
+
+
Child Content
+
+ )
+
+ // Change back to desktop view
+ window.matchMedia = jest.fn().mockImplementation((query) => ({
+ matches: false, // Change to false to simulate desktop
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
+ }))
+
+ // Call the effect manually to simulate the component re-rendering
+ const rerender = render(
+
+
Child Content
+
+ )
+
+ // Check that the last known states were restored (which were true initially)
+ expect(mockSetShowLeftPanel).toHaveBeenCalledWith(true)
+ expect(mockSetShowRightPanel).toHaveBeenCalledWith(true)
+ })
+})
diff --git a/web/containers/Providers/Theme.test.tsx b/web/containers/Providers/Theme.test.tsx
new file mode 100644
index 0000000000..552bbecbe5
--- /dev/null
+++ b/web/containers/Providers/Theme.test.tsx
@@ -0,0 +1,24 @@
+import '@testing-library/jest-dom'
+import React from 'react'
+import { render } from '@testing-library/react'
+import ThemeWrapper from './Theme'
+
+// Mock the ThemeProvider from next-themes
+jest.mock('next-themes', () => ({
+ ThemeProvider: ({ children }: { children: React.ReactNode }) => (
+