{}}
+ onClearError={() => {}}
+ />
+ );
+
+ expect(screen.getByTestId('error-title')).toHaveTextContent('Agent Not Available');
+ });
+ });
+
+ describe('Document Generation Flow', () => {
+ it('tracks generated documents', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ // Initially no documents
+ expect(result.current.generatedDocuments).toHaveLength(0);
+ expect(result.current.isGeneratingDocs).toBe(false);
+
+ // Set generating docs
+ await act(async () => {
+ result.current.setGeneratingDocs(true);
+ });
+ expect(result.current.isGeneratingDocs).toBe(true);
+
+ // Set generated documents
+ await act(async () => {
+ result.current.setGeneratedDocuments([
+ { filename: 'Phase-01-Setup.md', content: '# Phase 1', taskCount: 3 },
+ { filename: 'Phase-02-Auth.md', content: '# Phase 2', taskCount: 5 },
+ ]);
+ });
+
+ expect(result.current.generatedDocuments).toHaveLength(2);
+ expect(result.current.isGeneratingDocs).toBe(false);
+ });
+
+ it('tracks streaming content during generation', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ expect(result.current.streamingContent).toBe('');
+
+ // Streaming content updates happen during generateDocuments
+ // We can test the state structure is correct
+ expect(result.current.state).toHaveProperty('streamingContent');
+ expect(result.current.state).toHaveProperty('generationProgress');
+ });
+
+ it('tracks generation progress', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ expect(result.current.generationProgress).toBeNull();
+
+ // Progress tracking structure is available
+ expect(result.current.state).toHaveProperty('generationProgress');
+ });
+ });
+
+ describe('UI State Restoration', () => {
+ it('returns previous UI state when wizard ends', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+ const previousUIState = { readOnlyMode: true, saveToHistory: false, showThinking: true };
+
+ // Mock no docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({ success: true, files: [] });
+
+ // Start wizard with previous state
+ await act(async () => {
+ await result.current.startWizard(
+ undefined,
+ previousUIState,
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ });
+
+ expect(result.current.isWizardActive).toBe(true);
+
+ // End wizard and get previous state
+ let restoredState: any;
+ await act(async () => {
+ restoredState = await result.current.endWizard();
+ });
+
+ expect(result.current.isWizardActive).toBe(false);
+ expect(restoredState).toEqual(previousUIState);
+ });
+
+ it('resets all state when wizard ends', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ // Mock no docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({ success: true, files: [] });
+
+ // Start wizard
+ await act(async () => {
+ await result.current.startWizard(
+ undefined,
+ { readOnlyMode: false, saveToHistory: true, showThinking: true },
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ });
+
+ // Add some state
+ await act(async () => {
+ result.current.addAssistantMessage('Test', 50, false);
+ result.current.setConfidence(75);
+ });
+
+ expect(result.current.conversationHistory).toHaveLength(1);
+ expect(result.current.confidence).toBe(75);
+
+ // End wizard
+ await act(async () => {
+ await result.current.endWizard();
+ });
+
+ // All state should be reset
+ expect(result.current.isWizardActive).toBe(false);
+ expect(result.current.conversationHistory).toHaveLength(0);
+ expect(result.current.confidence).toBe(0);
+ expect(result.current.wizardMode).toBeNull();
+ expect(result.current.wizardGoal).toBeNull();
+ expect(result.current.error).toBeNull();
+ });
+
+ it('reset() clears all wizard state', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ // Mock no docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({ success: true, files: [] });
+
+ // Start wizard and add state
+ await act(async () => {
+ await result.current.startWizard(
+ undefined,
+ { readOnlyMode: false, saveToHistory: true, showThinking: true },
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ result.current.addAssistantMessage('Test', 60, false);
+ result.current.setError('Some error');
+ });
+
+ expect(result.current.isWizardActive).toBe(true);
+ expect(result.current.conversationHistory).toHaveLength(1);
+ expect(result.current.error).toBe('Some error');
+
+ // Reset
+ await act(async () => {
+ result.current.reset();
+ });
+
+ // All state should be cleared
+ expect(result.current.isWizardActive).toBe(false);
+ expect(result.current.conversationHistory).toHaveLength(0);
+ expect(result.current.error).toBeNull();
+ expect(result.current.state.previousUIState).toBeNull();
+ });
+ });
+
+ describe('Clear Error and Retry', () => {
+ it('clearError() clears the current error', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ await act(async () => {
+ result.current.setError('Test error');
+ });
+ expect(result.current.error).toBe('Test error');
+
+ await act(async () => {
+ result.current.clearError();
+ });
+ expect(result.current.error).toBeNull();
+ });
+
+ it('setError() sets the error message', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ await act(async () => {
+ result.current.setError('Connection failed');
+ });
+
+ expect(result.current.error).toBe('Connection failed');
+ });
+ });
+
+ describe('Context Provider', () => {
+ it('provides wizard state through context', async () => {
+ const TestComponent = () => {
+ const wizard = useInlineWizardContext();
+ return (
+
+ {wizard.isWizardActive.toString()}
+ {wizard.confidence}
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('is-active')).toHaveTextContent('false');
+ expect(screen.getByTestId('confidence')).toHaveTextContent('0');
+ });
+
+ it('throws error when used outside provider', () => {
+ const TestComponent = () => {
+ useInlineWizardContext();
+ return null;
+ };
+
+ // Suppress console.error for this test
+ const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ expect(() => render()).toThrow(
+ 'useInlineWizardContext must be used within an InlineWizardProvider'
+ );
+
+ consoleError.mockRestore();
+ });
+ });
+
+ describe('Streaming and Loading States', () => {
+ const theme = createMockTheme();
+
+ it('shows typing indicator when loading and no streaming text', () => {
+ render(
+
+ );
+
+ expect(screen.getByTestId('wizard-typing-indicator')).toBeInTheDocument();
+ });
+
+ it('shows streaming response when loading with streaming text', () => {
+ render(
+
+ );
+
+ expect(screen.getByTestId('wizard-streaming-response')).toBeInTheDocument();
+ expect(screen.getByTestId('streaming-response-text')).toHaveTextContent(
+ 'I am analyzing your...'
+ );
+ });
+
+ it('shows empty state when no messages and not loading', () => {
+ render(
+
+ );
+
+ expect(screen.getByTestId('wizard-conversation-empty')).toBeInTheDocument();
+ });
+
+ it('does not show empty state when there are messages', () => {
+ render(
+
+ );
+
+ expect(screen.queryByTestId('wizard-conversation-empty')).not.toBeInTheDocument();
+ });
+
+ it('does not show typing indicator when there is an error', () => {
+ render(
+
+ );
+
+ expect(screen.queryByTestId('wizard-typing-indicator')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('Mode Setting', () => {
+ it('setMode updates the wizard mode', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ // Mock no docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({ success: true, files: [] });
+
+ // Start wizard
+ await act(async () => {
+ await result.current.startWizard(
+ undefined,
+ { readOnlyMode: false, saveToHistory: true, showThinking: true },
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ });
+
+ expect(result.current.wizardMode).toBe('new');
+
+ // Change mode
+ await act(async () => {
+ result.current.setMode('iterate');
+ });
+
+ expect(result.current.wizardMode).toBe('iterate');
+ });
+
+ it('setGoal updates the iterate mode goal', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ expect(result.current.wizardGoal).toBeNull();
+
+ await act(async () => {
+ result.current.setGoal('add new feature');
+ });
+
+ expect(result.current.wizardGoal).toBe('add new feature');
+ });
+ });
+
+ describe('Existing Documents Handling', () => {
+ it('setExistingDocuments updates the existing documents list', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ expect(result.current.existingDocuments).toHaveLength(0);
+
+ await act(async () => {
+ result.current.setExistingDocuments([
+ { name: 'Phase-01', filename: 'Phase-01.md', path: '/test/Phase-01.md' },
+ { name: 'Phase-02', filename: 'Phase-02.md', path: '/test/Phase-02.md' },
+ ]);
+ });
+
+ expect(result.current.existingDocuments).toHaveLength(2);
+ });
+
+ it('loads existing documents in iterate mode', async () => {
+ // Mock existing docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({
+ success: true,
+ files: ['Phase-01-Setup', 'Phase-02-Development'],
+ });
+ mockMaestro.autorun.readDoc.mockResolvedValue({
+ success: true,
+ content: '# Phase 1\n- [ ] Task 1',
+ });
+
+ const { result } = renderHook(() => useInlineWizard());
+
+ await act(async () => {
+ await result.current.startWizard(
+ 'add authentication',
+ { readOnlyMode: false, saveToHistory: true, showThinking: true },
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ });
+
+ expect(result.current.wizardMode).toBe('iterate');
+ expect(result.current.existingDocuments).toHaveLength(2);
+ });
+ });
+
+ describe('Conversation History Management', () => {
+ it('clearConversation removes all messages', async () => {
+ const { result } = renderHook(() => useInlineWizard());
+
+ // Add messages
+ await act(async () => {
+ result.current.addAssistantMessage('Message 1', 30, false);
+ result.current.addAssistantMessage('Message 2', 50, false);
+ });
+
+ expect(result.current.conversationHistory).toHaveLength(2);
+
+ // Clear conversation
+ await act(async () => {
+ result.current.clearConversation();
+ });
+
+ expect(result.current.conversationHistory).toHaveLength(0);
+ });
+ });
+
+ describe('Full Integration Scenarios', () => {
+ it('simulates complete new wizard flow from start to document generation ready', async () => {
+ // Mock no existing docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({ success: true, files: [] });
+
+ const { result } = renderHook(() => useInlineWizard());
+ const previousUIState = { readOnlyMode: true, saveToHistory: false, showThinking: true };
+
+ // Step 1: Start wizard
+ await act(async () => {
+ await result.current.startWizard(
+ undefined,
+ previousUIState,
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ });
+
+ expect(result.current.isWizardActive).toBe(true);
+ expect(result.current.wizardMode).toBe('new');
+ expect(result.current.state.previousUIState).toEqual(previousUIState);
+
+ // Step 2: Simulate AI responses with increasing confidence
+ await act(async () => {
+ result.current.addAssistantMessage("What kind of project is this?", 30, false);
+ });
+
+ expect(result.current.confidence).toBe(30);
+ expect(result.current.ready).toBe(false);
+ expect(result.current.readyToGenerate).toBe(false);
+
+ await act(async () => {
+ result.current.addAssistantMessage("I see, it's a web app. Tell me more about the features.", 55, false);
+ });
+
+ expect(result.current.confidence).toBe(55);
+ expect(result.current.readyToGenerate).toBe(false);
+
+ await act(async () => {
+ result.current.addAssistantMessage("Great! I have a good understanding now.", 85, true);
+ });
+
+ // Step 3: Verify ready state
+ expect(result.current.confidence).toBe(85);
+ expect(result.current.ready).toBe(true);
+ expect(result.current.readyToGenerate).toBe(true);
+ expect(result.current.conversationHistory).toHaveLength(3);
+ });
+
+ it('simulates iterate flow with existing documents', async () => {
+ // Mock existing docs
+ mockMaestro.autorun.listDocs.mockResolvedValue({
+ success: true,
+ files: ['Phase-01-Setup'],
+ });
+ mockMaestro.autorun.readDoc.mockResolvedValue({
+ success: true,
+ content: '# Phase 1\n- [x] Task 1\n- [x] Task 2',
+ });
+
+ const { result } = renderHook(() => useInlineWizard());
+
+ // Start with iterate intent
+ await act(async () => {
+ await result.current.startWizard(
+ 'add user authentication and API endpoints',
+ { readOnlyMode: false, saveToHistory: true, showThinking: true },
+ '/test/project',
+ 'claude-code',
+ 'TestProject'
+ );
+ });
+
+ // Verify iterate mode
+ expect(result.current.wizardMode).toBe('iterate');
+ expect(result.current.wizardGoal).toBe('user authentication and API endpoints');
+ expect(result.current.existingDocuments).toHaveLength(1);
+ });
+ });
+});
diff --git a/src/__tests__/renderer/contexts/InlineWizardContext.test.tsx b/src/__tests__/renderer/contexts/InlineWizardContext.test.tsx
index c293f1d5..d5321e87 100644
--- a/src/__tests__/renderer/contexts/InlineWizardContext.test.tsx
+++ b/src/__tests__/renderer/contexts/InlineWizardContext.test.tsx
@@ -113,6 +113,9 @@ describe('InlineWizardContext', () => {
projectPath: null,
agentType: null,
sessionName: null,
+ streamingContent: '',
+ generationProgress: null,
+ lastUserMessageContent: null,
});
});
});
@@ -284,6 +287,9 @@ describe('InlineWizardContext', () => {
projectPath: null,
agentType: null,
sessionName: null,
+ streamingContent: '',
+ generationProgress: null,
+ lastUserMessageContent: null,
});
});
});
diff --git a/vitest.integration.config.ts b/vitest.integration.config.ts
index d7973406..a7fcf4d5 100644
--- a/vitest.integration.config.ts
+++ b/vitest.integration.config.ts
@@ -1,6 +1,6 @@
/**
* @file vitest.integration.config.ts
- * @description Vitest configuration for Group Chat integration tests.
+ * @description Vitest configuration for integration tests.
*
* Integration tests require real agents and exercise the full flow.
* These tests are meant to be run manually or in dedicated CI jobs.
@@ -9,14 +9,19 @@
*/
import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
+ plugins: [react()],
test: {
include: [
'src/__tests__/integration/**/*.integration.test.ts',
'src/__tests__/integration/**/provider-integration.test.ts',
+ 'src/__tests__/integration/**/*.test.tsx', // Include React component integration tests
],
+ environment: 'jsdom', // Required for React component tests
+ setupFiles: ['./src/__tests__/setup.ts'],
testTimeout: 180000, // 3 minutes per test
hookTimeout: 60000, // 1 minute for setup/teardown
pool: 'forks', // Use forks instead of threads for process isolation