- 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
200 lines
7.3 KiB
PHP
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();
|
|
});
|
|
});
|
|
|