Download desktop/src/components/layout/AppShell.test.tsx from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/layout/AppShell.test.tsx
- Command line
-
hf download hf://chenbhao/codev@e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/layout/AppShell.test.tsx
-
curl -L -o AppShell.test.tsx https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/components/layout/AppShell.test.tsx
11.2 kB
| import { fireEvent, render, screen, waitFor } from '@testing-library/react' | |
| import '@testing-library/jest-dom' | |
| import { beforeEach, describe, expect, it, vi } from 'vitest' | |
| import { useUIStore } from '../../stores/uiStore' | |
| import { useSessionStore } from '../../stores/sessionStore' | |
| const mocks = vi.hoisted(() => ({ | |
| initializeDesktopServerUrl: vi.fn(), | |
| isTauriRuntime: false, | |
| isMobile: false, | |
| fetchAll: vi.fn(), | |
| restoreTabs: vi.fn(), | |
| connectToSession: vi.fn(), | |
| setActiveTab: vi.fn(), | |
| tabState: { | |
| activeTabId: null as string | null, | |
| tabs: [] as Array<{ sessionId: string; title: string; type: string; status: string }>, | |
| }, | |
| })) | |
| vi.mock('../../lib/desktopRuntime', () => ({ | |
| initializeDesktopServerUrl: mocks.initializeDesktopServerUrl, | |
| isTauriRuntime: () => mocks.isTauriRuntime, | |
| isH5ConnectionRequiredError: (error: unknown) => | |
| error instanceof Error && error.name === 'H5ConnectionRequiredError', | |
| })) | |
| vi.mock('../../stores/settingsStore', () => ({ | |
| useSettingsStore: (selector: (state: { fetchAll: typeof mocks.fetchAll }) => unknown) => | |
| selector({ fetchAll: mocks.fetchAll }), | |
| })) | |
| vi.mock('../../hooks/useMobileViewport', () => ({ | |
| useMobileViewport: () => mocks.isMobile, | |
| })) | |
| vi.mock('../../stores/tabStore', () => { | |
| const useTabStore = (selector: (state: { | |
| tabs: typeof mocks.tabState.tabs | |
| activeTabId: string | null | |
| setActiveTab: typeof mocks.setActiveTab | |
| }) => unknown) => selector({ | |
| tabs: mocks.tabState.tabs, | |
| activeTabId: mocks.tabState.activeTabId, | |
| setActiveTab: mocks.setActiveTab, | |
| }) | |
| useTabStore.getState = () => ({ | |
| restoreTabs: mocks.restoreTabs, | |
| activeTabId: mocks.tabState.activeTabId, | |
| tabs: mocks.tabState.tabs, | |
| openTab: vi.fn(), | |
| setActiveTab: mocks.setActiveTab, | |
| }) | |
| useTabStore.setState = (next: { activeTabId?: string | null }) => { | |
| if ('activeTabId' in next) mocks.tabState.activeTabId = next.activeTabId ?? null | |
| } | |
| return { | |
| SETTINGS_TAB_ID: '__settings__', | |
| useTabStore, | |
| } | |
| }) | |
| vi.mock('../../stores/chatStore', () => ({ | |
| useChatStore: { | |
| getState: () => ({ | |
| connectToSession: mocks.connectToSession, | |
| }), | |
| }, | |
| })) | |
| vi.mock('../../hooks/useKeyboardShortcuts', () => ({ | |
| useKeyboardShortcuts: vi.fn(), | |
| })) | |
| vi.mock('../../i18n', () => ({ | |
| useTranslation: () => (key: string) => key, | |
| })) | |
| vi.mock('./Sidebar', () => ({ | |
| Sidebar: () => <aside>sidebar loaded</aside>, | |
| })) | |
| vi.mock('./ContentRouter', () => ({ | |
| ContentRouter: () => <section>content loaded</section>, | |
| })) | |
| vi.mock('./TabBar', () => ({ | |
| TabBar: () => <nav>tabs loaded</nav>, | |
| })) | |
| vi.mock('./H5ConnectionView', () => ({ | |
| H5ConnectionView: ({ error, onConnected }: { error?: string | null; onConnected: () => void }) => ( | |
| <div> | |
| <div>h5 connection view</div> | |
| <div>{error}</div> | |
| <button type="button" onClick={onConnected}>retry h5 bootstrap</button> | |
| </div> | |
| ), | |
| })) | |
| vi.mock('../shared/Toast', () => ({ | |
| ToastContainer: () => null, | |
| })) | |
| vi.mock('../shared/UpdateChecker', () => ({ | |
| UpdateChecker: () => <div>updates loaded</div>, | |
| })) | |
| import { AppShell } from './AppShell' | |
| describe('AppShell boot flow', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks() | |
| mocks.isTauriRuntime = false | |
| mocks.isMobile = false | |
| mocks.initializeDesktopServerUrl.mockResolvedValue('http://127.0.0.1:3456') | |
| mocks.fetchAll.mockResolvedValue(undefined) | |
| mocks.restoreTabs.mockResolvedValue(undefined) | |
| mocks.setActiveTab.mockImplementation((sessionId: string) => { | |
| mocks.tabState.activeTabId = sessionId | |
| }) | |
| mocks.tabState.activeTabId = null | |
| mocks.tabState.tabs = [] | |
| useSessionStore.setState({ sessions: [], activeSessionId: null, isLoading: false, error: null }) | |
| useUIStore.setState({ sidebarOpen: true }) | |
| }) | |
| it('renders the desktop chrome after server and settings bootstrap', async () => { | |
| render(<AppShell />) | |
| expect(screen.getByText('app.launching')).toBeInTheDocument() | |
| expect(await screen.findByText('sidebar loaded')).toBeInTheDocument() | |
| expect(screen.getByText('tabs loaded')).toBeInTheDocument() | |
| expect(screen.getByText('content loaded')).toBeInTheDocument() | |
| expect(screen.getByText('updates loaded')).toBeInTheDocument() | |
| }) | |
| it('shows startup diagnostics instead of a blank shell when bootstrap fails', async () => { | |
| mocks.fetchAll.mockRejectedValueOnce(new Error('settings file could not be read')) | |
| render(<AppShell />) | |
| expect(await screen.findByText('app.serverFailed')).toBeInTheDocument() | |
| expect(screen.getByText('settings file could not be read')).toBeInTheDocument() | |
| expect(screen.queryByText('sidebar loaded')).not.toBeInTheDocument() | |
| }) | |
| it('keeps the app usable when persisted tab restore fails', async () => { | |
| mocks.restoreTabs.mockRejectedValueOnce(new Error('old tab payload is invalid')) | |
| render(<AppShell />) | |
| expect(await screen.findByText('sidebar loaded')).toBeInTheDocument() | |
| await waitFor(() => { | |
| expect(mocks.restoreTabs).toHaveBeenCalled() | |
| }) | |
| expect(screen.queryByText('app.serverFailed')).not.toBeInTheDocument() | |
| }) | |
| it('reconnects the restored active session tab after boot', async () => { | |
| mocks.tabState.activeTabId = 'session-1' | |
| mocks.tabState.tabs = [ | |
| { | |
| sessionId: 'session-1', | |
| title: 'Existing session', | |
| type: 'session', | |
| status: 'idle', | |
| }, | |
| ] | |
| render(<AppShell />) | |
| await screen.findByText('sidebar loaded') | |
| await waitFor(() => { | |
| expect(mocks.connectToSession).toHaveBeenCalledWith('session-1') | |
| }) | |
| }) | |
| it('shows the H5 connection view in browser mode when startup needs H5 auth', async () => { | |
| mocks.initializeDesktopServerUrl.mockRejectedValueOnce( | |
| Object.assign(new Error('Enter your H5 token to continue.'), { | |
| name: 'H5ConnectionRequiredError', | |
| serverUrl: 'https://remote.example.com', | |
| }), | |
| ) | |
| render(<AppShell />) | |
| expect(await screen.findByText('h5 connection view')).toBeInTheDocument() | |
| expect(screen.getByText('Enter your H5 token to continue.')).toBeInTheDocument() | |
| expect(screen.queryByText('app.serverFailed')).not.toBeInTheDocument() | |
| }) | |
| it('shows the H5 connection view for unreachable remote browser startup failures', async () => { | |
| mocks.initializeDesktopServerUrl.mockRejectedValueOnce( | |
| Object.assign(new Error('Unable to reach https://remote.example.com. Check the server URL or network access.'), { | |
| name: 'H5ConnectionRequiredError', | |
| serverUrl: 'https://remote.example.com', | |
| }), | |
| ) | |
| render(<AppShell />) | |
| expect(await screen.findByText('h5 connection view')).toBeInTheDocument() | |
| expect(screen.getByText('Unable to reach https://remote.example.com. Check the server URL or network access.')).toBeInTheDocument() | |
| expect(screen.queryByText('app.serverFailed')).not.toBeInTheDocument() | |
| }) | |
| it('retries bootstrap after a successful H5 connection', async () => { | |
| mocks.initializeDesktopServerUrl | |
| .mockRejectedValueOnce( | |
| Object.assign(new Error('The saved H5 token is no longer valid.'), { | |
| name: 'H5ConnectionRequiredError', | |
| serverUrl: 'https://remote.example.com', | |
| }), | |
| ) | |
| .mockResolvedValueOnce('https://remote.example.com') | |
| render(<AppShell />) | |
| expect(await screen.findByText('h5 connection view')).toBeInTheDocument() | |
| fireEvent.click(screen.getByRole('button', { name: 'retry h5 bootstrap' })) | |
| await screen.findByText('sidebar loaded') | |
| expect(mocks.initializeDesktopServerUrl).toHaveBeenCalledTimes(2) | |
| expect(mocks.fetchAll).toHaveBeenCalledTimes(1) | |
| }) | |
| it('keeps the Tauri startup error path unchanged', async () => { | |
| mocks.isTauriRuntime = true | |
| mocks.initializeDesktopServerUrl.mockRejectedValueOnce( | |
| Object.assign(new Error('desktop server startup failed'), { | |
| name: 'H5ConnectionRequiredError', | |
| serverUrl: 'https://remote.example.com', | |
| }), | |
| ) | |
| render(<AppShell />) | |
| expect(await screen.findByText('app.serverFailed')).toBeInTheDocument() | |
| expect(screen.queryByText('h5 connection view')).not.toBeInTheDocument() | |
| }) | |
| it('renders a mobile drawer toggle and backdrop in browser H5 mode', async () => { | |
| mocks.isMobile = true | |
| render(<AppShell />) | |
| await screen.findByText('content loaded') | |
| await waitFor(() => { | |
| expect(useUIStore.getState().sidebarOpen).toBe(false) | |
| }) | |
| expect(screen.getByTestId('sidebar-shell')).toHaveAttribute('data-state', 'closed') | |
| expect(screen.getByTestId('sidebar-shell')).toHaveAttribute('aria-hidden', 'true') | |
| expect(screen.getByTestId('sidebar-shell')).toHaveAttribute('inert') | |
| expect(screen.queryByText('sidebar loaded')).not.toBeInTheDocument() | |
| expect(screen.getByTestId('mobile-sidebar-toggle')).toBeInTheDocument() | |
| expect(screen.queryByTestId('sidebar-backdrop')).not.toBeInTheDocument() | |
| fireEvent.click(screen.getByTestId('mobile-sidebar-toggle')) | |
| expect(useUIStore.getState().sidebarOpen).toBe(true) | |
| expect(screen.getByTestId('sidebar-shell')).toHaveAttribute('data-state', 'open') | |
| expect(screen.getByText('sidebar loaded')).toBeInTheDocument() | |
| expect(screen.getByTestId('sidebar-backdrop')).toBeInTheDocument() | |
| fireEvent.click(screen.getByTestId('sidebar-backdrop')) | |
| expect(useUIStore.getState().sidebarOpen).toBe(false) | |
| expect(screen.getByTestId('sidebar-shell')).toHaveAttribute('data-state', 'closed') | |
| }) | |
| it('shares the mobile drawer row with the active session title', async () => { | |
| mocks.isMobile = true | |
| mocks.tabState.activeTabId = 'session-mobile' | |
| mocks.tabState.tabs = [ | |
| { sessionId: 'session-mobile', title: 'Fallback tab title', type: 'session', status: 'running' }, | |
| ] | |
| useSessionStore.setState({ | |
| sessions: [{ | |
| id: 'session-mobile', | |
| title: 'Analyze recent commits', | |
| createdAt: '2026-05-10T00:00:00.000Z', | |
| modifiedAt: new Date().toISOString(), | |
| messageCount: 7, | |
| projectPath: '/tmp/project', | |
| workDir: '/tmp/project', | |
| workDirExists: true, | |
| }], | |
| activeSessionId: 'session-mobile', | |
| isLoading: false, | |
| error: null, | |
| }) | |
| render(<AppShell />) | |
| await screen.findByText('content loaded') | |
| const header = screen.getByTestId('mobile-session-header') | |
| expect(header).toHaveTextContent('Analyze recent commits') | |
| expect(header).toHaveTextContent('session.active') | |
| expect(header).toHaveTextContent('session.messages') | |
| expect(screen.getByTestId('mobile-sidebar-toggle')).toHaveClass('h-10', 'w-10') | |
| }) | |
| it('keeps browser H5 mobile on chat tabs when settings was restored as active', async () => { | |
| mocks.isMobile = true | |
| mocks.tabState.activeTabId = '__settings__' | |
| mocks.tabState.tabs = [ | |
| { sessionId: '__settings__', title: 'Settings', type: 'settings', status: 'idle' }, | |
| { sessionId: 'session-1', title: 'Existing session', type: 'session', status: 'idle' }, | |
| ] | |
| render(<AppShell />) | |
| await screen.findByText('content loaded') | |
| expect(screen.queryByText('tabs loaded')).not.toBeInTheDocument() | |
| await waitFor(() => { | |
| expect(mocks.setActiveTab).toHaveBeenCalledWith('session-1') | |
| }) | |
| }) | |
| }) | |