- 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
388 lines
12 KiB
PHP
388 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Console;
|
|
|
|
use App\Framework\Console\ArgumentDefinition;
|
|
use App\Framework\Console\ArgumentType;
|
|
use App\Framework\Console\ParsedArguments;
|
|
|
|
describe('ParsedArguments', function () {
|
|
it('gets argument values', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(
|
|
['name' => 'John'],
|
|
[],
|
|
$definitions
|
|
);
|
|
|
|
expect($parsed->get('name'))->toBe('John');
|
|
});
|
|
|
|
it('gets option values', function () {
|
|
$definitions = [
|
|
'verbose' => new ArgumentDefinition('verbose', ArgumentType::BOOLEAN),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(
|
|
[],
|
|
['verbose' => true],
|
|
$definitions
|
|
);
|
|
|
|
expect($parsed->get('verbose'))->toBeTrue();
|
|
});
|
|
|
|
it('returns default value when argument is missing', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING, default: 'Guest'),
|
|
];
|
|
|
|
$parsed = new ParsedArguments([], [], $definitions);
|
|
|
|
expect($parsed->get('name'))->toBe('Guest');
|
|
});
|
|
|
|
it('returns null when argument is missing and no default', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING),
|
|
];
|
|
|
|
$parsed = new ParsedArguments([], [], $definitions);
|
|
|
|
expect($parsed->get('name'))->toBeNull();
|
|
});
|
|
|
|
it('requires argument and throws exception when missing', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING, required: true),
|
|
];
|
|
|
|
$parsed = new ParsedArguments([], [], $definitions);
|
|
|
|
expect(fn () => $parsed->require('name'))
|
|
->toThrow(\InvalidArgumentException::class, "Required argument 'name' is missing");
|
|
});
|
|
|
|
it('requires argument and returns value when present', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING, required: true),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['name' => 'John'], [], $definitions);
|
|
|
|
expect($parsed->require('name'))->toBe('John');
|
|
});
|
|
|
|
it('gets typed values', function () {
|
|
$definitions = [
|
|
'count' => new ArgumentDefinition('count', ArgumentType::INTEGER),
|
|
'active' => new ArgumentDefinition('active', ArgumentType::BOOLEAN),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(
|
|
['count' => '42', 'active' => 'true'],
|
|
[],
|
|
$definitions
|
|
);
|
|
|
|
expect($parsed->getTyped('count'))->toBe(42);
|
|
expect($parsed->getTyped('active'))->toBeTrue();
|
|
});
|
|
|
|
it('gets string value', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['name' => 'John'], [], $definitions);
|
|
|
|
expect($parsed->getString('name'))->toBe('John');
|
|
expect($parsed->getString('name'))->toBeString();
|
|
});
|
|
|
|
it('gets integer value', function () {
|
|
$definitions = [
|
|
'count' => new ArgumentDefinition('count', ArgumentType::INTEGER),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['count' => '42'], [], $definitions);
|
|
|
|
expect($parsed->getInt('count'))->toBe(42);
|
|
expect($parsed->getInt('count'))->toBeInt();
|
|
});
|
|
|
|
it('throws exception for invalid integer', function () {
|
|
$definitions = [
|
|
'count' => new ArgumentDefinition('count', ArgumentType::INTEGER),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['count' => 'not-a-number'], [], $definitions);
|
|
|
|
expect(fn () => $parsed->getInt('count'))
|
|
->toThrow(\InvalidArgumentException::class, 'not a valid integer');
|
|
});
|
|
|
|
it('gets float value', function () {
|
|
$definitions = [
|
|
'price' => new ArgumentDefinition('price', ArgumentType::FLOAT),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['price' => '12.34'], [], $definitions);
|
|
|
|
expect($parsed->getFloat('price'))->toBe(12.34);
|
|
expect($parsed->getFloat('price'))->toBeFloat();
|
|
});
|
|
|
|
it('throws exception for invalid float', function () {
|
|
$definitions = [
|
|
'price' => new ArgumentDefinition('price', ArgumentType::FLOAT),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['price' => 'not-a-number'], [], $definitions);
|
|
|
|
expect(fn () => $parsed->getFloat('price'))
|
|
->toThrow(\InvalidArgumentException::class, 'not a valid number');
|
|
});
|
|
|
|
it('gets boolean value', function () {
|
|
$definitions = [
|
|
'active' => new ArgumentDefinition('active', ArgumentType::BOOLEAN),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['active' => 'true'], [], $definitions);
|
|
|
|
expect($parsed->getBool('active'))->toBeTrue();
|
|
});
|
|
|
|
it('parses various boolean string values', function () {
|
|
$definitions = [
|
|
'flag' => new ArgumentDefinition('flag', ArgumentType::BOOLEAN),
|
|
];
|
|
|
|
$trueValues = ['true', '1', 'yes', 'on'];
|
|
foreach ($trueValues as $value) {
|
|
$parsed = new ParsedArguments(['flag' => $value], [], $definitions);
|
|
expect($parsed->getBool('flag'))->toBeTrue();
|
|
}
|
|
});
|
|
|
|
it('gets array value', function () {
|
|
$definitions = [
|
|
'items' => new ArgumentDefinition('items', ArgumentType::ARRAY),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['items' => 'item1,item2,item3'], [], $definitions);
|
|
|
|
expect($parsed->getArray('items'))->toBe(['item1', 'item2', 'item3']);
|
|
});
|
|
|
|
it('handles array value that is already an array', function () {
|
|
$definitions = [
|
|
'items' => new ArgumentDefinition('items', ArgumentType::ARRAY),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['items' => ['item1', 'item2']], [], $definitions);
|
|
|
|
expect($parsed->getArray('items'))->toBe(['item1', 'item2']);
|
|
});
|
|
|
|
it('gets email value object', function () {
|
|
$definitions = [
|
|
'email' => new ArgumentDefinition('email', ArgumentType::EMAIL),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['email' => 'user@example.com'], [], $definitions);
|
|
|
|
$email = $parsed->getEmail('email');
|
|
expect($email)->toBeInstanceOf(\App\Framework\Core\ValueObjects\EmailAddress::class);
|
|
expect($email->toString())->toBe('user@example.com');
|
|
});
|
|
|
|
it('throws exception for invalid email', function () {
|
|
$definitions = [
|
|
'email' => new ArgumentDefinition('email', ArgumentType::EMAIL),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['email' => 'invalid-email'], [], $definitions);
|
|
|
|
expect(fn () => $parsed->getEmail('email'))
|
|
->toThrow(\InvalidArgumentException::class, 'not a valid email address');
|
|
});
|
|
|
|
it('gets URL value object', function () {
|
|
$definitions = [
|
|
'url' => new ArgumentDefinition('url', ArgumentType::URL),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['url' => 'https://example.com'], [], $definitions);
|
|
|
|
$url = $parsed->getUrl('url');
|
|
expect($url)->toBeInstanceOf(\App\Framework\Http\Url\Url::class);
|
|
});
|
|
|
|
it('throws exception for invalid URL', function () {
|
|
$definitions = [
|
|
'url' => new ArgumentDefinition('url', ArgumentType::URL),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['url' => 'not-a-url'], [], $definitions);
|
|
|
|
expect(fn () => $parsed->getUrl('url'))
|
|
->toThrow(\InvalidArgumentException::class, 'not a valid URL');
|
|
});
|
|
|
|
it('checks if argument exists', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['name' => 'John'], [], $definitions);
|
|
|
|
expect($parsed->has('name'))->toBeTrue();
|
|
expect($parsed->has('missing'))->toBeFalse();
|
|
});
|
|
|
|
it('checks if argument has value', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING),
|
|
'empty' => new ArgumentDefinition('empty', ArgumentType::STRING),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(
|
|
['name' => 'John', 'empty' => ''],
|
|
[],
|
|
$definitions
|
|
);
|
|
|
|
expect($parsed->hasValue('name'))->toBeTrue();
|
|
expect($parsed->hasValue('empty'))->toBeFalse();
|
|
expect($parsed->hasValue('missing'))->toBeFalse();
|
|
});
|
|
|
|
it('validates required arguments', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING, required: true),
|
|
'optional' => new ArgumentDefinition('optional', ArgumentType::STRING),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['name' => 'John'], [], $definitions);
|
|
|
|
// Should not throw
|
|
$parsed->validate();
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('throws exception when required argument is missing', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING, required: true),
|
|
];
|
|
|
|
$parsed = new ParsedArguments([], [], $definitions);
|
|
|
|
expect(fn () => $parsed->validate())
|
|
->toThrow(\InvalidArgumentException::class, "Required argument 'name' is missing");
|
|
});
|
|
|
|
it('validates allowed values', function () {
|
|
$definitions = [
|
|
'mode' => new ArgumentDefinition('mode', ArgumentType::STRING, allowedValues: ['dev', 'prod', 'test']),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['mode' => 'dev'], [], $definitions);
|
|
|
|
// Should not throw
|
|
$parsed->validate();
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('throws exception for invalid allowed value', function () {
|
|
$definitions = [
|
|
'mode' => new ArgumentDefinition('mode', ArgumentType::STRING, allowedValues: ['dev', 'prod', 'test']),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['mode' => 'invalid'], [], $definitions);
|
|
|
|
expect(fn () => $parsed->validate())
|
|
->toThrow(\InvalidArgumentException::class, "Invalid value 'invalid'");
|
|
});
|
|
|
|
it('returns all arguments', function () {
|
|
$parsed = new ParsedArguments(
|
|
['arg1' => 'value1', 'arg2' => 'value2'],
|
|
[],
|
|
[]
|
|
);
|
|
|
|
$all = $parsed->getAllArguments();
|
|
expect($all)->toHaveKey('arg1');
|
|
expect($all)->toHaveKey('arg2');
|
|
expect($all['arg1'])->toBe('value1');
|
|
});
|
|
|
|
it('returns all options', function () {
|
|
$parsed = new ParsedArguments(
|
|
[],
|
|
['opt1' => 'value1', 'opt2' => 'value2'],
|
|
[]
|
|
);
|
|
|
|
$all = $parsed->getAllOptions();
|
|
expect($all)->toHaveKey('opt1');
|
|
expect($all)->toHaveKey('opt2');
|
|
expect($all['opt1'])->toBe('value1');
|
|
});
|
|
|
|
it('returns all definitions', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING),
|
|
'count' => new ArgumentDefinition('count', ArgumentType::INTEGER),
|
|
];
|
|
|
|
$parsed = new ParsedArguments([], [], $definitions);
|
|
|
|
$all = $parsed->getDefinitions();
|
|
expect($all)->toHaveKey('name');
|
|
expect($all)->toHaveKey('count');
|
|
});
|
|
|
|
it('handles empty string as missing value', function () {
|
|
$definitions = [
|
|
'name' => new ArgumentDefinition('name', ArgumentType::STRING, required: true),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['name' => ''], [], $definitions);
|
|
|
|
expect(fn () => $parsed->validate())
|
|
->toThrow(\InvalidArgumentException::class, "Required argument 'name' is missing");
|
|
});
|
|
|
|
it('handles boolean false correctly', function () {
|
|
$definitions = [
|
|
'active' => new ArgumentDefinition('active', ArgumentType::BOOLEAN),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['active' => false], [], $definitions);
|
|
|
|
expect($parsed->getBool('active'))->toBeFalse();
|
|
expect($parsed->hasValue('active'))->toBeTrue();
|
|
});
|
|
|
|
it('handles empty array correctly', function () {
|
|
$definitions = [
|
|
'items' => new ArgumentDefinition('items', ArgumentType::ARRAY),
|
|
];
|
|
|
|
$parsed = new ParsedArguments(['items' => []], [], $definitions);
|
|
|
|
expect($parsed->getArray('items'))->toBe([]);
|
|
// Empty array is considered a value (it's not null or empty string)
|
|
expect($parsed->hasValue('items'))->toBeTrue();
|
|
});
|
|
});
|
|
|