/** * @vitest-environment happy-dom */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { render as rtlRender, act } from '@testing-library/react'; import React from 'react'; import { BranchCreatorScreen } from '../../../components/screens/BranchCreatorScreen.js'; import { Window } from 'happy-dom'; import { render as inkRender } from 'ink-testing-library'; describe('BranchCreatorScreen', () => { beforeEach(() => { vi.useFakeTimers(); // Setup happy-dom const window = new Window(); globalThis.window = window as any; globalThis.document = window.document as any; }); afterEach(() => { vi.useRealTimers(); }); it('should render header with title', () => { const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { getByText } = rtlRender( ); expect(getByText(/New Branch/i)).toBeDefined(); }); it('should render branch type selection initially', () => { const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { getByText } = rtlRender( ); expect(getByText(/Select branch type/i)).toBeDefined(); expect(getByText(/feature/i)).toBeDefined(); expect(getByText(/hotfix/i)).toBeDefined(); expect(getByText(/release/i)).toBeDefined(); }); it('should render footer with actions', () => { const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { getAllByText } = rtlRender( ); expect(getAllByText(/enter/i).length).toBeGreaterThan(0); expect(getAllByText(/esc/i).length).toBeGreaterThan(0); }); it('should show branch name input after type selection', () => { const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { container } = rtlRender( ); // Test will verify the screen transitions from type selection to name input expect(container).toBeDefined(); }); it('should handle branch creation', () => { const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { container } = rtlRender( ); // Test will verify onCreate is called with correct branch name expect(container).toBeDefined(); }); it('should use terminal height for layout calculation', () => { const originalRows = process.stdout.rows; process.stdout.rows = 30; const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { container } = rtlRender( ); expect(container).toBeDefined(); process.stdout.rows = originalRows; }); it('should handle back navigation with ESC key', () => { const onBack = vi.fn(); const onCreate = vi.fn().mockResolvedValue(undefined); const { container } = rtlRender( ); // Test will verify onBack is called when ESC is pressed expect(container).toBeDefined(); }); it('should display creating state while waiting for branch creation', async () => { expect.assertions(3); const onBack = vi.fn(); let resolveCreate: (() => void) | null = null; const onCreate = vi.fn( () => new Promise((resolve) => { resolveCreate = resolve; }) ); const { stdin, lastFrame } = inkRender( ); // Select default branch type (feature) await act(async () => { stdin.write('\r'); }); await act(async () => { await Promise.resolve(); }); const branchName = 'new-branch'; for (const char of branchName) { await act(async () => { stdin.write(char); }); } await act(async () => { await Promise.resolve(); }); // Submit branch name await act(async () => { stdin.write('\r'); }); await act(async () => { await Promise.resolve(); }); expect(onCreate).toHaveBeenCalledWith(`feature/${branchName}`); expect(lastFrame()).toContain('Creating branch'); expect(lastFrame()).toContain(`feature/${branchName}`); await act(async () => { resolveCreate?.(); }); await act(async () => { await Promise.resolve(); }); }); it('should ignore ESC input while branch creation is in progress', async () => { expect.assertions(2); const onBack = vi.fn(); let resolveCreate: (() => void) | null = null; const onCreate = vi.fn( () => new Promise((resolve) => { resolveCreate = resolve; }) ); const { stdin, lastFrame } = inkRender( ); // Move to name input await act(async () => { stdin.write('\r'); }); await act(async () => { await Promise.resolve(); }); const branchName = 'blocking-branch'; for (const char of branchName) { await act(async () => { stdin.write(char); }); } await act(async () => { await Promise.resolve(); }); await act(async () => { stdin.write('\r'); }); await act(async () => { await Promise.resolve(); }); // Attempt to cancel with ESC during creation await act(async () => { stdin.write('\u001B'); }); await act(async () => { await Promise.resolve(); }); expect(onBack).not.toHaveBeenCalled(); expect(lastFrame()).toContain('Creating branch'); await act(async () => { resolveCreate?.(); }); await act(async () => { await Promise.resolve(); }); }); });