diff --git a/web-app/src/routes/settings/__tests__/hardware.test.tsx b/web-app/src/routes/settings/__tests__/hardware.test.tsx new file mode 100644 index 000000000..831821310 --- /dev/null +++ b/web-app/src/routes/settings/__tests__/hardware.test.tsx @@ -0,0 +1,174 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { render, screen, waitFor } from '@testing-library/react' +import '@testing-library/jest-dom' + +// Mock all the dependencies with minimal implementation +vi.mock('@/containers/SettingsMenu', () => ({ + default: () =>
Settings Menu
, +})) + +vi.mock('@/containers/HeaderPage', () => ({ + default: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), +})) + +vi.mock('@/containers/Card', () => ({ + Card: ({ title, children }: { title?: string; children: React.ReactNode }) => ( +
+ {title &&
{title}
} + {children} +
+ ), + CardItem: ({ title, actions }: { title?: string; actions?: React.ReactNode }) => ( +
+ {title &&
{title}
} + {actions} +
+ ), +})) + +vi.mock('@/components/ui/switch', () => ({ + Switch: ({ checked }: { checked: boolean }) => ( + + ), +})) + +vi.mock('@/components/ui/progress', () => ({ + Progress: ({ value }: { value: number }) => ( +
Progress: {value}%
+ ), +})) + +vi.mock('@/i18n/react-i18next-compat', () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})) + +vi.mock('@/hooks/useHardware', () => ({ + useHardware: () => ({ + hardwareData: { + os_type: 'windows', + os_name: 'Windows 11', + cpu: { name: 'Intel i7', arch: 'x64', core_count: 8, extensions: ['SSE'] }, + total_memory: 16384, + }, + systemUsage: { cpu: 50, used_memory: 8192 }, + setHardwareData: vi.fn(), + updateSystemUsage: vi.fn(), + pollingPaused: false, + }), +})) + +vi.mock('@/hooks/useLlamacppDevices', () => ({ + useLlamacppDevices: () => ({ + devices: [{ id: 'gpu0', name: 'RTX 3080', mem: 10240, free: 8192 }], + loading: false, + error: null, + activatedDevices: new Set(['gpu0']), + toggleDevice: vi.fn(), + fetchDevices: vi.fn(), + }), + getState: () => ({ setActivatedDevices: vi.fn() }), +})) + +vi.mock('@/hooks/useModelProvider', () => ({ + useModelProvider: () => ({ + providers: [{ provider: 'llamacpp' }], + getProviderByName: vi.fn(() => ({ settings: [{ key: 'device', controller_props: { value: 'gpu0' } }] })), + }), +})) + +vi.mock('@/services/hardware', () => ({ + getHardwareInfo: vi.fn(() => Promise.resolve({})), + getSystemUsage: vi.fn(() => Promise.resolve({})), +})) + +vi.mock('@/services/models', () => ({ stopAllModels: vi.fn() })) +vi.mock('@/lib/utils', () => ({ formatMegaBytes: (mb: number) => `${mb} MB` })) +vi.mock('@/utils/number', () => ({ toNumber: (n: number) => n })) +vi.mock('@tauri-apps/api/webviewWindow', () => ({ WebviewWindow: vi.fn() })) +vi.mock('@/constants/routes', () => ({ + route: { + settings: { + hardware: '/settings/hardware' + }, + systemMonitor: '/monitor' + } +})) +vi.mock('@/constants/windows', () => ({ windowKey: { systemMonitorWindow: 'monitor' } })) +vi.mock('@tabler/icons-react', () => ({ IconDeviceDesktopAnalytics: () =>
})) + +// Mock the route structure properly +vi.mock('@tanstack/react-router', () => ({ + createFileRoute: () => (config: any) => config, +})) + +global.IS_MACOS = false + +// Import the actual component after all mocks are set up +import { Route } from '../hardware' + +describe('Hardware Settings', () => { + beforeEach(() => { + vi.clearAllMocks() + global.IS_MACOS = false + }) + + it('renders hardware settings page', () => { + const Component = Route.component as React.ComponentType + render() + + expect(screen.getByTestId('header-page')).toBeInTheDocument() + expect(screen.getByTestId('settings-menu')).toBeInTheDocument() + }) + + it('displays OS information', async () => { + const Component = Route.component as React.ComponentType + render() + + await waitFor(() => { + expect(screen.getByText('settings:hardware.os')).toBeInTheDocument() + expect(screen.getByText('windows')).toBeInTheDocument() + }) + }) + + it('displays CPU information', async () => { + const Component = Route.component as React.ComponentType + render() + + await waitFor(() => { + expect(screen.getByText('settings:hardware.cpu')).toBeInTheDocument() + expect(screen.getByText('Intel i7')).toBeInTheDocument() + }) + }) + + it('displays memory information', async () => { + const Component = Route.component as React.ComponentType + render() + + await waitFor(() => { + expect(screen.getByText('settings:hardware.memory')).toBeInTheDocument() + }) + }) + + it('displays GPU devices on non-macOS', async () => { + global.IS_MACOS = false + const Component = Route.component as React.ComponentType + render() + + await waitFor(() => { + expect(screen.getByText('GPUs')).toBeInTheDocument() + expect(screen.getByText('RTX 3080')).toBeInTheDocument() + }) + }) + + it('hides GPU devices on macOS', async () => { + global.IS_MACOS = true + const Component = Route.component as React.ComponentType + render() + + await waitFor(() => { + expect(screen.queryByText('GPUs')).not.toBeInTheDocument() + }) + }) +}) \ No newline at end of file diff --git a/web-app/src/routes/settings/hardware.tsx b/web-app/src/routes/settings/hardware.tsx index 226455fb1..a69063809 100644 --- a/web-app/src/routes/settings/hardware.tsx +++ b/web-app/src/routes/settings/hardware.tsx @@ -296,8 +296,9 @@ function Hardware() { {( toNumber( - systemUsage.used_memory / - systemUsage.total_memory + (hardwareData.total_memory - + systemUsage.used_memory) / + hardwareData.total_memory ) * 100 ).toFixed(2)} % diff --git a/web-app/src/routes/system-monitor.tsx b/web-app/src/routes/system-monitor.tsx index f2ede3899..6c36c21d7 100644 --- a/web-app/src/routes/system-monitor.tsx +++ b/web-app/src/routes/system-monitor.tsx @@ -179,9 +179,7 @@ function SystemMonitor() { {t('system-monitor:usedRam')} - {formatMegaBytes( - hardwareData.total_memory - systemUsage.used_memory - )} + {formatMegaBytes(systemUsage.used_memory)}