/**
* @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();
});