Files
michaelschiemer/tests/Framework/Console/ConsoleInputTest.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

200 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\Console;
use App\Framework\Console\ArgumentParser;
use App\Framework\Console\ArgumentParserBuilder;
use App\Framework\Console\ArgumentType;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ConsoleOutput;
describe('ConsoleInput', function () {
beforeEach(function () {
$this->output = new ConsoleOutput();
});
it('parses simple arguments', function () {
$input = new ConsoleInput(['arg1', 'arg2', 'arg3'], $this->output);
expect($input->getArgument(0))->toBe('arg1');
expect($input->getArgument(1))->toBe('arg2');
expect($input->getArgument(2))->toBe('arg3');
});
it('returns default value for missing arguments', function () {
$input = new ConsoleInput(['arg1'], $this->output);
expect($input->getArgument(0))->toBe('arg1');
expect($input->getArgument(1, 'default'))->toBe('default');
expect($input->getArgument(2))->toBeNull();
});
it('parses long options with equals sign', function () {
$input = new ConsoleInput(['--option=value', '--flag'], $this->output);
expect($input->getOption('option'))->toBe('value');
expect($input->hasOption('flag'))->toBeTrue();
expect($input->getOption('flag'))->toBeTrue();
});
it('parses long options with space', function () {
// Note: Simple parser doesn't support --option value, only --option=value
// This test verifies the current behavior
$input = new ConsoleInput(['--option', 'value'], $this->output);
// Simple parser treats 'value' as a separate argument, not as option value
expect($input->hasOption('option'))->toBeTrue();
expect($input->getArgument(0))->toBe('value');
});
it('parses short options', function () {
$input = new ConsoleInput(['-f', '-o', 'value'], $this->output);
expect($input->hasOption('f'))->toBeTrue();
// Simple parser treats 'value' as argument, not as option value
expect($input->getArgument(0))->toBe('value');
});
it('parses mixed arguments and options', function () {
$input = new ConsoleInput(['arg1', '--option=value', 'arg2', '-f'], $this->output);
expect($input->getArgument(0))->toBe('arg1');
expect($input->getArgument(1))->toBe('arg2');
expect($input->getOption('option'))->toBe('value');
expect($input->hasOption('f'))->toBeTrue();
});
it('returns all arguments', function () {
$input = new ConsoleInput(['arg1', 'arg2', 'arg3'], $this->output);
$args = $input->getArguments();
expect($args)->toBe(['arg1', 'arg2', 'arg3']);
});
it('returns all options', function () {
$input = new ConsoleInput(['--opt1=val1', '--opt2', '-f'], $this->output);
$options = $input->getOptions();
expect($options)->toHaveKey('opt1');
expect($options)->toHaveKey('opt2');
expect($options)->toHaveKey('f');
});
it('returns default value for missing options', function () {
$input = new ConsoleInput([], $this->output);
expect($input->getOption('missing', 'default'))->toBe('default');
expect($input->getOption('missing'))->toBeNull();
});
it('supports enhanced parsing with ArgumentParser', function () {
$parser = ArgumentParser::create()
->requiredString('name')
->integer('age', required: false, default: 18)
->build();
$input = new ConsoleInput(['--name=John', '--age=25'], $this->output, $parser);
expect($input->hasEnhancedParsing())->toBeTrue();
expect($input->getString('name'))->toBe('John');
expect($input->getInt('age'))->toBe(25);
});
it('throws exception when accessing enhanced parsing without parser', function () {
$input = new ConsoleInput(['--option=value'], $this->output);
expect($input->hasEnhancedParsing())->toBeFalse();
expect(fn () => $input->getParsedArguments())
->toThrow(\RuntimeException::class, 'Enhanced parsing not available');
});
it('validates required arguments with enhanced parsing', function () {
$parser = ArgumentParser::create()
->requiredString('name')
->build();
$input = new ConsoleInput(['--name=John'], $this->output, $parser);
expect($input->require('name'))->toBe('John');
});
it('throws exception for missing required arguments', function () {
$parser = ArgumentParser::create()
->requiredString('name')
->build();
// Exception is thrown during parsing, not when calling require()
expect(fn () => new ConsoleInput([], $this->output, $parser))
->toThrow(\InvalidArgumentException::class, "Required argument 'name' is missing");
});
it('converts types correctly with enhanced parsing', function () {
$parser = ArgumentParser::create()
->integer('count')
->addArgument(new \App\Framework\Console\ArgumentDefinition('active', ArgumentType::BOOLEAN))
->addArgument(new \App\Framework\Console\ArgumentDefinition('items', ArgumentType::ARRAY))
->build();
$input = new ConsoleInput([
'--count=42',
'--active=true',
'--items=item1,item2,item3'
], $this->output, $parser);
expect($input->getInt('count'))->toBe(42);
expect($input->getBool('active'))->toBeTrue();
expect($input->getArray('items'))->toBe(['item1', 'item2', 'item3']);
});
it('handles kebab-case option names', function () {
$input = new ConsoleInput(['--dry-run', '--output-file=test.txt'], $this->output);
expect($input->hasOption('dry-run'))->toBeTrue();
expect($input->getOption('output-file'))->toBe('test.txt');
});
it('can set argument parser after construction', function () {
$input = new ConsoleInput(['--name=John'], $this->output);
$parser = ArgumentParser::create()
->optionalString('name')
->build();
$input->setArgumentParser($parser);
expect($input->hasEnhancedParsing())->toBeTrue();
// Note: setArgumentParser re-parses, but needs the raw arguments
// The simple parser already parsed --name=John, so we can verify it's available
expect($input->getOption('name'))->toBe('John');
});
});
describe('ConsoleInput Interactive Methods', function () {
it('has ask method that delegates to InteractivePrompter', function () {
$output = new ConsoleOutput();
$input = new ConsoleInput([], $output);
// Verify method exists - actual interactive behavior requires STDIN
expect(method_exists($input, 'ask'))->toBeTrue();
});
it('has confirm method that delegates to InteractivePrompter', function () {
$output = new ConsoleOutput();
$input = new ConsoleInput([], $output);
// Verify method exists - actual interactive behavior requires STDIN
expect(method_exists($input, 'confirm'))->toBeTrue();
});
it('has askPassword method that delegates to InteractivePrompter', function () {
$output = new ConsoleOutput();
$input = new ConsoleInput([], $output);
// Verify method exists - actual interactive behavior requires STDIN
expect(method_exists($input, 'askPassword'))->toBeTrue();
});
});