output = new TestConsoleOutput(); }); it('creates progress tracker', function () { $tracker = new ProgressTracker($this->output, 100, 'Test Progress'); expect($tracker)->toBeInstanceOf(ProgressTracker::class); }); it('creates progress tracker with default title', function () { $tracker = new ProgressTracker($this->output, 100); expect($tracker)->toBeInstanceOf(ProgressTracker::class); }); it('ensures total is at least 1', function () { $tracker = new ProgressTracker($this->output, 0); // Should not throw, total should be adjusted to 1 expect($tracker)->toBeInstanceOf(ProgressTracker::class); }); it('renders initial progress', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); expect($this->output->capturedWrites)->not->toBeEmpty(); }); it('advances progress', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->advance(10); expect($tracker->getProgress())->toBeGreaterThan(0); }); it('advances by custom step', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->advance(25); expect($tracker->getProgress())->toBe(0.25); }); it('advances with task description', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->advance(10, 'Processing item 1'); expect($tracker->getProgress())->toBeGreaterThan(0); }); it('sets progress directly', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(50); expect($tracker->getProgress())->toBe(0.5); }); it('sets progress with task', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(50, 'Halfway done'); expect($tracker->getProgress())->toBe(0.5); }); it('does not exceed total', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(150); expect($tracker->getProgress())->toBe(1.0); }); it('does not go below zero', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(-10); expect($tracker->getProgress())->toBe(0.0); }); it('sets task separately', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setTask('Current task'); expect($tracker)->toBeInstanceOf(ProgressTracker::class); }); it('finishes progress', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->finish(); expect($tracker->isFinished())->toBeTrue(); expect($tracker->getProgress())->toBe(1.0); }); it('finishes with message', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->finish('Completed!'); expect($tracker->isFinished())->toBeTrue(); expect($this->output->capturedLines)->not->toBeEmpty(); }); it('does not advance when finished', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->finish(); $tracker->advance(10); expect($tracker->isFinished())->toBeTrue(); }); it('does not set progress when finished', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->finish(); $tracker->setProgress(50); expect($tracker->isFinished())->toBeTrue(); }); it('gets progress as float', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(50); expect($tracker->getProgress())->toBe(0.5); expect($tracker->getProgress())->toBeFloat(); }); it('gets elapsed time', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); usleep(100000); // 100ms $elapsed = $tracker->getElapsedTime(); expect($elapsed)->toBeGreaterThan(0); expect($elapsed)->toBeFloat(); }); it('gets estimated time remaining', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(50); usleep(100000); // 100ms $eta = $tracker->getEstimatedTimeRemaining(); expect($eta)->not->toBeNull(); expect($eta)->toBeFloat(); expect($eta)->toBeGreaterThan(0); }); it('returns null for estimated time when at start', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); expect($tracker->getEstimatedTimeRemaining())->toBeNull(); }); it('sets total dynamically', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setTotal(200); expect($tracker->getProgress())->toBeLessThan(0.1); // Progress should be recalculated }); it('ensures total is at least 1 when setting', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setTotal(0); // Should not throw, total should be adjusted to 1 expect($tracker)->toBeInstanceOf(ProgressTracker::class); }); it('automatically finishes when progress reaches total', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->setProgress(100); expect($tracker->isFinished())->toBeTrue(); }); it('automatically finishes when advance reaches total', function () { $tracker = new ProgressTracker($this->output, 100, 'Test'); $tracker->advance(100); expect($tracker->isFinished())->toBeTrue(); }); it('handles very large total', function () { $tracker = new ProgressTracker($this->output, 1000000, 'Test'); $tracker->advance(100000); expect($tracker->getProgress())->toBe(0.1); }); it('handles edge case with total of 1', function () { $tracker = new ProgressTracker($this->output, 1, 'Test'); $tracker->advance(1); expect($tracker->isFinished())->toBeTrue(); expect($tracker->getProgress())->toBe(1.0); }); }); describe('ProgressType', function () { it('has all expected enum values', function () { expect(\App\Framework\Console\Progress\ProgressType::AUTO->value)->toBe('auto'); expect(\App\Framework\Console\Progress\ProgressType::TRACKER->value)->toBe('tracker'); expect(\App\Framework\Console\Progress\ProgressType::SPINNER->value)->toBe('spinner'); expect(\App\Framework\Console\Progress\ProgressType::BAR->value)->toBe('bar'); expect(\App\Framework\Console\Progress\ProgressType::NONE->value)->toBe('none'); }); it('provides descriptions for all types', function () { expect(\App\Framework\Console\Progress\ProgressType::AUTO->getDescription())->toBe('Automatically select based on operation type'); expect(\App\Framework\Console\Progress\ProgressType::TRACKER->getDescription())->toBe('Detailed progress tracker with time estimates'); expect(\App\Framework\Console\Progress\ProgressType::SPINNER->getDescription())->toBe('Spinner for indeterminate operations'); expect(\App\Framework\Console\Progress\ProgressType::BAR->getDescription())->toBe('Simple progress bar'); expect(\App\Framework\Console\Progress\ProgressType::NONE->getDescription())->toBe('No progress indication'); }); });