- Fix Enter key detection: handle multiple Enter key formats (\n, \r, \r\n) - Reduce flickering: lower render frequency from 60 FPS to 30 FPS - Fix menu bar visibility: re-render menu bar after content to prevent overwriting - Fix content positioning: explicit line positioning for categories and commands - Fix line shifting: clear lines before writing, control newlines manually - Limit visible items: prevent overflow with maxVisibleCategories/Commands - Improve CPU usage: increase sleep interval when no events processed This fixes: - Enter key not working for selection - Strong flickering of the application - Menu bar not visible or being overwritten - Top half of selection list not displayed - Lines being shifted/misaligned
225 lines
7.7 KiB
PHP
225 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Console\Progress;
|
|
|
|
use App\Framework\Console\Progress\ProgressTracker;
|
|
use Tests\Framework\Console\Helpers\TestConsoleOutput;
|
|
|
|
describe('ProgressTracker', function () {
|
|
beforeEach(function () {
|
|
$this->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');
|
|
});
|
|
});
|
|
|