Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion ui/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"start-alpha-gui": "ALPHA=true npm run start-gui"
},
"dependencies": {
"@tanstack/react-form": "^0.13.0",
"@ai-sdk/openai": "^2.0.14",
"@ai-sdk/ui-utils": "^1.2.11",
"@mcp-ui/client": "^5.9.0",
Expand Down
62 changes: 31 additions & 31 deletions ui/desktop/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* @vitest-environment jsdom
*/
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { screen, render, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import App from './App';
import { AppInner } from './App';

// Set up globals for jsdom
Object.defineProperty(window, 'location', {
Expand Down Expand Up @@ -161,13 +161,19 @@ vi.mock('./components/AnnouncementModal', () => ({
default: () => null,
}));

// Create mocks that we can track and configure per test
const mockNavigate = vi.fn();
const mockSearchParams = new URLSearchParams();
const mockSetSearchParams = vi.fn();

// Mock react-router-dom to avoid HashRouter issues in tests
vi.mock('react-router-dom', () => ({
HashRouter: ({ children }: { children: React.ReactNode }) => <>{children}</>,
Routes: ({ children }: { children: React.ReactNode }) => <>{children}</>,
Route: ({ element }: { element: React.ReactNode }) => element,
useNavigate: () => vi.fn(),
useNavigate: () => mockNavigate,
useLocation: () => ({ state: null, pathname: '/' }),
useSearchParams: () => [mockSearchParams, mockSetSearchParams],
Outlet: () => null,
}));

Expand Down Expand Up @@ -216,6 +222,14 @@ Object.defineProperty(window, 'matchMedia', {
describe('App Component - Brand New State', () => {
beforeEach(() => {
vi.clearAllMocks();
mockNavigate.mockClear();
mockSetSearchParams.mockClear();

// Reset search params
mockSearchParams.forEach((_, key) => {
mockSearchParams.delete(key);
});

window.location.hash = '';
window.location.search = '';
window.location.pathname = '/';
Expand All @@ -235,21 +249,16 @@ describe('App Component - Brand New State', () => {
GOOSE_ALLOWLIST_WARNING: false,
});

render(<App />);
render(<AppInner />);

// Wait for initialization
await waitFor(() => {
expect(mockElectron.reactReady).toHaveBeenCalled();
});

// Check that we navigated to "/" not "/welcome"
await waitFor(() => {
// In some environments, the hash might be empty or just "#"
expect(window.location.hash).toMatch(/^(#\/?|)$/);
});

// History should have been updated to "/"
expect(window.history.replaceState).toHaveBeenCalledWith({}, '', '#/');
// The app should initialize without any navigation calls since we're already at "/"
// No navigate calls should be made when no provider is configured
expect(mockNavigate).not.toHaveBeenCalled();
});

it('should handle deep links correctly when app is brand new', async () => {
Expand All @@ -260,20 +269,17 @@ describe('App Component - Brand New State', () => {
GOOSE_ALLOWLIST_WARNING: false,
});

// Simulate a deep link
window.location.search = '?view=settings';
// Set up search params to simulate view=settings deep link
mockSearchParams.set('view', 'settings');

render(<App />);
render(<AppInner />);

// Wait for initialization
await waitFor(() => {
expect(mockElectron.reactReady).toHaveBeenCalled();
});

// Should redirect to settings route via hash
await waitFor(() => {
expect(window.location.hash).toBe('#/settings');
});
expect(screen.getByText(/^Select an AI model provider/)).toBeInTheDocument();
});

it('should not redirect to /welcome when provider is configured', async () => {
Expand All @@ -284,18 +290,15 @@ describe('App Component - Brand New State', () => {
GOOSE_ALLOWLIST_WARNING: false,
});

render(<App />);
render(<AppInner />);

// Wait for initialization
await waitFor(() => {
expect(mockElectron.reactReady).toHaveBeenCalled();
});

// Should stay at "/" since provider is configured
await waitFor(() => {
// In some environments, the hash might be empty or just "#"
expect(window.location.hash).toMatch(/^(#\/?|)$/);
});
// Should not navigate anywhere since provider is configured and we're already at "/"
expect(mockNavigate).not.toHaveBeenCalled();
});

it('should handle config recovery gracefully', async () => {
Expand All @@ -310,17 +313,14 @@ describe('App Component - Brand New State', () => {
GOOSE_ALLOWLIST_WARNING: false,
});

render(<App />);
render(<AppInner />);

// Wait for initialization and recovery
await waitFor(() => {
expect(mockElectron.reactReady).toHaveBeenCalled();
});

// App should still initialize and navigate to "/"
await waitFor(() => {
// In some environments, the hash might be empty or just "#"
expect(window.location.hash).toMatch(/^(#\/?|)$/);
});
// App should still initialize without any navigation calls
expect(mockNavigate).not.toHaveBeenCalled();
});
});
Loading
Loading