Add end-to-end tests for workspace switching and backend tests for ask_question tool

- Implemented E2E tests for workspace switching functionality, covering scenarios such as switching workspaces, data isolation, language preference maintenance, and UI updates.
- Added tests to ensure workspace data is cleared on logout and handles unsaved changes during workspace switches.
- Created comprehensive backend tests for the ask_question tool, validating question creation, execution, answer handling, cancellation, and timeout scenarios.
- Included edge case tests to ensure robustness against duplicate questions and invalid answers.
This commit is contained in:
catlog22
2026-01-31 16:02:20 +08:00
parent 715ef12c92
commit 345437415f
33 changed files with 7049 additions and 105 deletions

View File

@@ -10,7 +10,8 @@ import { Sidebar } from './Sidebar';
import { MainContent } from './MainContent';
import { CliStreamMonitor } from '@/components/shared/CliStreamMonitor';
import { NotificationPanel } from '@/components/notification';
import { useNotificationStore } from '@/stores';
import { AskQuestionDialog } from '@/components/a2ui/AskQuestionDialog';
import { useNotificationStore, selectCurrentQuestion } from '@/stores';
import { useWebSocketNotifications } from '@/hooks';
export interface AppShellProps {
@@ -57,6 +58,10 @@ export function AppShell({
(state) => state.loadPersistentNotifications
);
// Current question dialog state
const currentQuestion = useNotificationStore(selectCurrentQuestion);
const setCurrentQuestion = useNotificationStore((state) => state.setCurrentQuestion);
// Initialize WebSocket notifications handler
useWebSocketNotifications();
@@ -106,6 +111,10 @@ export function AppShell({
useNotificationStore.getState().setPanelVisible(false);
}, []);
const handleQuestionDialogClose = useCallback(() => {
setCurrentQuestion(null);
}, [setCurrentQuestion]);
return (
<div className="flex flex-col min-h-screen bg-background">
{/* Header - fixed at top */}
@@ -150,6 +159,14 @@ export function AppShell({
isOpen={isNotificationPanelVisible}
onClose={handleNotificationPanelClose}
/>
{/* Ask Question Dialog - For ask_question MCP tool */}
{currentQuestion && (
<AskQuestionDialog
payload={currentQuestion}
onClose={handleQuestionDialogClose}
/>
)}
</div>
);
}

View File

@@ -16,6 +16,7 @@ import {
User,
LogOut,
Terminal,
Bell,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/Button';
@@ -24,6 +25,7 @@ import { useTheme } from '@/hooks';
import { LanguageSwitcher } from './LanguageSwitcher';
import { WorkspaceSelector } from '@/components/workspace/WorkspaceSelector';
import { useCliStreamStore, selectActiveExecutionCount } from '@/stores/cliStreamStore';
import { useNotificationStore } from '@/stores';
export interface HeaderProps {
/** Callback to toggle mobile sidebar */
@@ -49,6 +51,13 @@ export function Header({
const { isDark, toggleTheme } = useTheme();
const activeCliCount = useCliStreamStore(selectActiveExecutionCount);
// Notification state for badge
const persistentNotifications = useNotificationStore((state) => state.persistentNotifications);
const togglePanel = useNotificationStore((state) => state.togglePanel);
// Calculate unread count
const unreadCount = persistentNotifications.filter((n) => !n.read).length;
const handleRefresh = useCallback(() => {
if (onRefresh && !isRefreshing) {
onRefresh();
@@ -105,6 +114,23 @@ export function Header({
{/* Workspace selector */}
{projectPath && <WorkspaceSelector />}
{/* Notification badge */}
<Button
variant="ghost"
size="icon"
onClick={togglePanel}
aria-label={formatMessage({ id: 'common.aria.notifications' }) || 'Notifications'}
title={formatMessage({ id: 'common.aria.notifications' }) || 'Notifications'}
className="relative"
>
<Bell className="w-5 h-5" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-destructive text-[10px] text-destructive-foreground flex items-center justify-center font-medium">
{unreadCount > 9 ? '9+' : unreadCount}
</span>
)}
</Button>
{/* Refresh button */}
{onRefresh && (
<Button