- 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
238 lines
8.3 KiB
PHP
238 lines
8.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Console;
|
|
|
|
use App\Framework\Console\CommandParameterResolver;
|
|
use App\Framework\Console\ConsoleInput;
|
|
use App\Framework\Console\ConsoleOutput;
|
|
use App\Framework\Console\MethodSignatureAnalyzer;
|
|
use ReflectionMethod;
|
|
|
|
class TestCommandClass
|
|
{
|
|
public function simpleMethod(): void
|
|
{
|
|
}
|
|
|
|
public function methodWithString(string $name): void
|
|
{
|
|
}
|
|
|
|
public function methodWithInt(int $count): void
|
|
{
|
|
}
|
|
|
|
public function methodWithBool(bool $active): void
|
|
{
|
|
}
|
|
|
|
public function methodWithArray(array $items): void
|
|
{
|
|
}
|
|
|
|
public function methodWithDefault(string $name = 'Guest'): void
|
|
{
|
|
}
|
|
|
|
public function methodWithNullable(?string $name): void
|
|
{
|
|
}
|
|
|
|
public function methodWithConsoleInput(ConsoleInput $input): void
|
|
{
|
|
}
|
|
|
|
public function methodWithConsoleOutput(ConsoleOutput $output): void
|
|
{
|
|
}
|
|
|
|
public function methodWithBoth(ConsoleInput $input, ConsoleOutput $output, string $name): void
|
|
{
|
|
}
|
|
|
|
public function methodWithOptionalFrameworkParams(string $name, ?ConsoleInput $input = null): void
|
|
{
|
|
}
|
|
}
|
|
|
|
describe('CommandParameterResolver', function () {
|
|
beforeEach(function () {
|
|
$this->analyzer = new MethodSignatureAnalyzer();
|
|
$this->resolver = new CommandParameterResolver($this->analyzer);
|
|
});
|
|
|
|
it('resolves simple string parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithString');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--name=John']);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBe('John');
|
|
});
|
|
|
|
it('resolves integer parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithInt');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--count=42']);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBe(42);
|
|
expect($resolved[0])->toBeInt();
|
|
});
|
|
|
|
it('resolves boolean parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithBool');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--active=true']);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBeTrue();
|
|
});
|
|
|
|
it('resolves array parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithArray');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--items=item1,item2,item3']);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBe(['item1', 'item2', 'item3']);
|
|
});
|
|
|
|
it('uses default values for optional parameters', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithDefault');
|
|
$resolved = $this->resolver->resolveParameters($method, []);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBe('Guest');
|
|
});
|
|
|
|
it('resolves nullable parameters as null when missing', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithNullable');
|
|
$resolved = $this->resolver->resolveParameters($method, []);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBeNull();
|
|
});
|
|
|
|
it('resolves ConsoleInput parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithConsoleInput');
|
|
$input = new ConsoleInput([]);
|
|
$resolved = $this->resolver->resolveParameters($method, [], $input);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBe($input);
|
|
});
|
|
|
|
it('resolves ConsoleOutput parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithConsoleOutput');
|
|
$output = new ConsoleOutput();
|
|
$resolved = $this->resolver->resolveParameters($method, [], null, $output);
|
|
|
|
expect($resolved)->toHaveCount(1);
|
|
expect($resolved[0])->toBe($output);
|
|
});
|
|
|
|
it('resolves both ConsoleInput and ConsoleOutput', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithBoth');
|
|
$input = new ConsoleInput([]);
|
|
$output = new ConsoleOutput();
|
|
$resolved = $this->resolver->resolveParameters($method, ['--name=John'], $input, $output);
|
|
|
|
expect($resolved)->toHaveCount(3);
|
|
expect($resolved[0])->toBe($input);
|
|
expect($resolved[1])->toBe($output);
|
|
expect($resolved[2])->toBe('John');
|
|
});
|
|
|
|
it('handles optional framework parameters', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithOptionalFrameworkParams');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--name=John']);
|
|
|
|
expect($resolved)->toHaveCount(2);
|
|
expect($resolved[0])->toBe('John');
|
|
expect($resolved[1])->toBeNull();
|
|
});
|
|
|
|
it('throws exception for missing required parameter', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithString');
|
|
|
|
expect(fn () => $this->resolver->resolveParameters($method, []))
|
|
->toThrow(\InvalidArgumentException::class, "Required parameter 'name' is missing");
|
|
});
|
|
|
|
it('throws exception when ConsoleInput is required but not provided', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithConsoleInput');
|
|
|
|
expect(fn () => $this->resolver->resolveParameters($method, []))
|
|
->toThrow(\InvalidArgumentException::class, "ConsoleInput is required but not provided");
|
|
});
|
|
|
|
it('validates method signature compatibility', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'simpleMethod');
|
|
|
|
// Should not throw for valid method
|
|
$this->resolver->validateMethodSignature($method);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('creates parser for method', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithString');
|
|
$parser = $this->resolver->createParserForMethod($method);
|
|
|
|
expect($parser)->toBeInstanceOf(\App\Framework\Console\ArgumentParser::class);
|
|
});
|
|
});
|
|
|
|
describe('CommandParameterResolver Type Conversion', function () {
|
|
beforeEach(function () {
|
|
$this->analyzer = new MethodSignatureAnalyzer();
|
|
$this->resolver = new CommandParameterResolver($this->analyzer);
|
|
});
|
|
|
|
it('converts string values correctly', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithString');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--name=123']);
|
|
|
|
expect($resolved[0])->toBe('123');
|
|
expect($resolved[0])->toBeString();
|
|
});
|
|
|
|
it('converts integer values correctly', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithInt');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--count=42']);
|
|
|
|
expect($resolved[0])->toBe(42);
|
|
expect($resolved[0])->toBeInt();
|
|
});
|
|
|
|
it('converts boolean values correctly', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithBool');
|
|
|
|
$resolved = $this->resolver->resolveParameters($method, ['--active=true']);
|
|
expect($resolved[0])->toBeTrue();
|
|
|
|
$resolved = $this->resolver->resolveParameters($method, ['--active=false']);
|
|
expect($resolved[0])->toBeFalse();
|
|
|
|
$resolved = $this->resolver->resolveParameters($method, ['--active=1']);
|
|
expect($resolved[0])->toBeTrue();
|
|
|
|
$resolved = $this->resolver->resolveParameters($method, ['--active=0']);
|
|
expect($resolved[0])->toBeFalse();
|
|
});
|
|
|
|
it('converts array values correctly', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithArray');
|
|
$resolved = $this->resolver->resolveParameters($method, ['--items=item1,item2,item3']);
|
|
|
|
expect($resolved[0])->toBeArray();
|
|
expect($resolved[0])->toBe(['item1', 'item2', 'item3']);
|
|
});
|
|
|
|
it('throws exception for invalid type conversion', function () {
|
|
$method = new ReflectionMethod(TestCommandClass::class, 'methodWithInt');
|
|
|
|
expect(fn () => $this->resolver->resolveParameters($method, ['--count=not-a-number']))
|
|
->toThrow(\InvalidArgumentException::class);
|
|
});
|
|
});
|
|
|