Files
michaelschiemer/tests/Framework/Console/CommandParameterResolverTest.php
Michael Schiemer 8f3c15ddbb fix(console): comprehensive TUI rendering fixes
- 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
2025-11-10 11:06:07 +01:00

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);
});
});