/** * @vitest-environment happy-dom * Edge case tests for UI components */ import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest'; import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { App } from '../../components/App.js'; import { BranchListScreen } from '../../components/screens/BranchListScreen.js'; import { Window } from 'happy-dom'; import type { BranchInfo, BranchItem, Statistics } from '../../types.js'; import * as useGitDataModule from '../../hooks/useGitData.js'; const mockRefresh = vi.fn(); const originalUseGitData = useGitDataModule.useGitData; const useGitDataSpy = vi.spyOn(useGitDataModule, 'useGitData'); describe('Edge Cases Integration Tests', () => { beforeEach(() => { // Setup happy-dom const window = new Window(); globalThis.window = window as any; globalThis.document = window.document as any; // Reset mocks vi.clearAllMocks(); useGitDataSpy.mockReset(); useGitDataSpy.mockImplementation(originalUseGitData); }); /** * T091: Terminal size極小(10行以下)の動作確認 */ it('[T091] should handle minimal terminal size (10 rows)', () => { // Save original rows const originalRows = process.stdout.rows; // Set minimal terminal size process.stdout.rows = 10; const mockBranches: BranchItem[] = [ { name: 'main', label: 'main', value: 'main' }, { name: 'feature/a', label: 'feature/a', value: 'feature/a' }, { name: 'feature/b', label: 'feature/b', value: 'feature/b' }, ]; const mockStats: Statistics = { localCount: 3, remoteCount: 0, worktreeCount: 0, changesCount: 0, lastUpdated: new Date(), }; const onSelect = vi.fn(); const { container } = render( ); // Should render without crashing expect(container).toBeDefined(); // Restore original rows process.stdout.rows = originalRows; }); it('[T091] should handle extremely small terminal (5 rows)', () => { const originalRows = process.stdout.rows; process.stdout.rows = 5; const mockBranches: BranchItem[] = [ { name: 'main', label: 'main', value: 'main' }, ]; const mockStats: Statistics = { localCount: 1, remoteCount: 0, worktreeCount: 0, changesCount: 0, lastUpdated: new Date(), }; const onSelect = vi.fn(); const { getByText } = render( ); // Header should still be visible expect(getByText(/Claude Worktree/i)).toBeDefined(); process.stdout.rows = originalRows; }); /** * T092: 非常に長いブランチ名の表示確認 */ it('[T092] should handle very long branch names', () => { const longBranchName = 'feature/very-long-branch-name-that-exceeds-normal-terminal-width-and-should-be-handled-gracefully'; const mockBranches: BranchItem[] = [ { name: 'main', label: 'main', value: 'main', latestCommitTimestamp: 1_700_000_000, }, { name: longBranchName, label: longBranchName, value: longBranchName, latestCommitTimestamp: 1_700_000_600, }, ]; const mockStats: Statistics = { localCount: 2, remoteCount: 0, worktreeCount: 0, changesCount: 0, lastUpdated: new Date(), }; const onSelect = vi.fn(); const { container } = render( ); // Long branch name should be displayed (Ink will handle wrapping/truncation) expect(container.textContent).toMatch(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/); }); it('[T092] should handle branch names with special characters', () => { const specialBranchNames = [ 'feature/bug-fix-#123', 'hotfix/issue@456', 'release/v1.0.0-beta.1', 'feature/改善-日本語', ]; const mockBranches: BranchItem[] = specialBranchNames.map((name, index) => ({ name, label: name, value: name, latestCommitTimestamp: 1_700_001_000 + index * 60, })); const mockStats: Statistics = { localCount: mockBranches.length, remoteCount: 0, worktreeCount: 0, changesCount: 0, lastUpdated: new Date(), }; const onSelect = vi.fn(); const { container } = render( ); // All special branch names should be displayed specialBranchNames.forEach((name) => { expect(container.textContent).toContain(name); }); }); /** * T093: Error Boundary動作確認 */ it('[T093] should catch errors in App component', async () => { // Mock useGitData to throw an error after initial render let callCount = 0; useGitDataSpy.mockImplementation(() => { callCount++; if (callCount > 1) { throw new Error('Simulated error'); } return { branches: [], worktrees: [], loading: false, error: null, refresh: mockRefresh, lastUpdated: null, }; }); const onExit = vi.fn(); const { container } = render(); // Initial render should work expect(container).toBeDefined(); }); it('[T093] should display error message when data loading fails', () => { const testError = new Error('Test error: Failed to load Git data'); useGitDataSpy.mockReturnValue({ branches: [], worktrees: [], loading: false, error: testError, refresh: mockRefresh, lastUpdated: null, }); const onExit = vi.fn(); const { getByText } = render(); // Error should be displayed expect(getByText(/Error:/i)).toBeDefined(); expect(getByText(/Failed to load Git data/i)).toBeDefined(); }); it('[T093] should handle empty branches list gracefully', () => { useGitDataSpy.mockReturnValue({ branches: [], worktrees: [], loading: false, error: null, refresh: mockRefresh, lastUpdated: null, }); const onExit = vi.fn(); const { container } = render(); // Should render without error even with no branches expect(container).toBeDefined(); }); /** * Additional edge cases */ it('should handle large number of worktrees', () => { const mockBranches: BranchInfo[] = Array.from({ length: 50 }, (_, i) => ({ name: `feature/branch-${i}`, type: 'local' as const, branchType: 'feature' as const, isCurrent: false, })); useGitDataSpy.mockReturnValue({ branches: mockBranches, worktrees: Array.from({ length: 30 }, (_, i) => ({ branch: `feature/branch-${i}`, path: `/path/to/worktree-${i}`, head: `commit-${i}`, isAccessible: true, })), loading: false, error: null, refresh: mockRefresh, lastUpdated: new Date(), }); const onExit = vi.fn(); const { container } = render(); expect(container).toBeDefined(); }); it('should handle terminal resize gracefully', () => { const originalRows = process.stdout.rows; // Start with normal size process.stdout.rows = 30; const mockBranches: BranchItem[] = [ { name: 'main', label: 'main', value: 'main' }, ]; const mockStats: Statistics = { localCount: 1, remoteCount: 0, worktreeCount: 0, changesCount: 0, lastUpdated: new Date(), }; const onSelect = vi.fn(); const { container, rerender } = render( ); expect(container).toBeDefined(); // Simulate terminal resize process.stdout.rows = 15; // Re-render rerender(); expect(container).toBeDefined(); process.stdout.rows = originalRows; }); afterEach(() => { useGitDataSpy.mockReset(); useGitDataSpy.mockImplementation(originalUseGitData); }); }); afterAll(() => { useGitDataSpy.mockRestore(); });