/**
* @vitest-environment happy-dom
*/
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
import { act, render } from '@testing-library/react';
import React from 'react';
import { App } from '../../components/App.js';
import { Window } from 'happy-dom';
import type { BranchInfo } 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('App', () => {
beforeEach(() => {
// Setup happy-dom
const window = new Window();
globalThis.window = window as any;
globalThis.document = window.document as any;
vi.clearAllMocks();
useGitDataSpy.mockReset();
useGitDataSpy.mockImplementation(originalUseGitData);
});
const mockBranches: BranchInfo[] = [
{
name: 'main',
type: 'local',
branchType: 'main',
isCurrent: true,
},
{
name: 'feature/test',
type: 'local',
branchType: 'feature',
isCurrent: false,
},
];
it('should render BranchListScreen when data is loaded', () => {
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { getByText } = render();
// Check for BranchListScreen elements
expect(getByText(/Claude Worktree/i)).toBeDefined();
expect(getByText(/main/)).toBeDefined();
expect(getByText(/feature\/test/)).toBeDefined();
});
it('should show loading state initially', async () => {
useGitDataSpy.mockReturnValue({
branches: [],
loading: true,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { queryByText, getByText } = render(
);
expect(queryByText(/Git情報を読み込んでいます/i)).toBeNull();
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 15));
});
expect(getByText(/Git情報を読み込んでいます/i)).toBeDefined();
});
it('should show error state when Git data fails to load', () => {
const error = new Error('Failed to fetch branches');
useGitDataSpy.mockReturnValue({
branches: [],
loading: false,
error,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { getByText } = render();
expect(getByText(/Error:/i)).toBeDefined();
expect(getByText(/Failed to fetch branches/i)).toBeDefined();
});
it('should calculate statistics from branches', () => {
const branchesWithWorktree: BranchInfo[] = [
{
name: 'main',
type: 'local',
branchType: 'main',
isCurrent: true,
},
{
name: 'feature/a',
type: 'local',
branchType: 'feature',
isCurrent: false,
worktree: {
path: '/path/a',
locked: false,
prunable: false,
},
},
{
name: 'origin/main',
type: 'remote',
branchType: 'main',
isCurrent: false,
},
];
useGitDataSpy.mockReturnValue({
branches: branchesWithWorktree,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { getByText, getAllByText } = render();
// Check for statistics
expect(getByText(/Local:/)).toBeDefined();
expect(getAllByText(/2/).length).toBeGreaterThan(0); // 2 local branches
expect(getByText(/Remote:/)).toBeDefined();
expect(getAllByText(/1/).length).toBeGreaterThan(0); // 1 remote branch + 1 worktree
expect(getByText(/Worktrees:/)).toBeDefined();
});
it('should call onExit when branch is selected', () => {
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { container } = render();
expect(container).toBeDefined();
// Note: Testing actual selection requires simulating user input,
// which is covered in integration tests
});
it('should handle empty branch list', () => {
useGitDataSpy.mockReturnValue({
branches: [],
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { getByText } = render();
expect(getByText(/No branches found/i)).toBeDefined();
});
it('should wrap with ErrorBoundary', () => {
// This test verifies ErrorBoundary is present
// Actual error catching is tested separately
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { container } = render();
expect(container).toBeDefined();
});
it('should format branch items with icons', () => {
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { getByText } = render();
// Check for branch type icon (main = ⚡)
expect(getByText(/⚡/)).toBeDefined();
});
describe('BranchActionSelectorScreen integration', () => {
it('should show BranchActionSelectorScreen after branch selection', () => {
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { container } = render();
// After implementation, should verify BranchActionSelectorScreen appears
expect(container).toBeDefined();
});
it('should navigate to AI tool selector when "use existing" is selected', () => {
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { container } = render();
// After implementation, should verify navigation to AIToolSelectorScreen
expect(container).toBeDefined();
});
it('should navigate to branch creator when "create new" is selected', () => {
useGitDataSpy.mockReturnValue({
branches: mockBranches,
loading: false,
error: null,
worktrees: [],
refresh: mockRefresh,
});
const onExit = vi.fn();
const { container } = render();
// After implementation, should verify navigation to BranchCreatorScreen
expect(container).toBeDefined();
});
});
afterEach(() => {
useGitDataSpy.mockReset();
useGitDataSpy.mockImplementation(originalUseGitData);
});
});
afterAll(() => {
useGitDataSpy.mockRestore();
});