- 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
197 lines
7.0 KiB
PHP
197 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Console;
|
|
|
|
use App\Framework\Console\ArgumentDefinition;
|
|
use App\Framework\Console\ArgumentType;
|
|
|
|
describe('ArgumentDefinition', function () {
|
|
it('creates required string argument', function () {
|
|
$def = ArgumentDefinition::required('name', 'User name');
|
|
|
|
expect($def->name)->toBe('name');
|
|
expect($def->type)->toBe(ArgumentType::STRING);
|
|
expect($def->required)->toBeTrue();
|
|
expect($def->default)->toBeNull();
|
|
expect($def->description)->toBe('User name');
|
|
});
|
|
|
|
it('creates optional string argument with default', function () {
|
|
$def = ArgumentDefinition::optional('name', 'Guest', 'User name');
|
|
|
|
expect($def->name)->toBe('name');
|
|
expect($def->type)->toBe(ArgumentType::STRING);
|
|
expect($def->required)->toBeFalse();
|
|
expect($def->default)->toBe('Guest');
|
|
expect($def->description)->toBe('User name');
|
|
});
|
|
|
|
it('creates boolean flag', function () {
|
|
$def = ArgumentDefinition::flag('verbose', 'v', 'Enable verbose output');
|
|
|
|
expect($def->name)->toBe('verbose');
|
|
expect($def->type)->toBe(ArgumentType::BOOLEAN);
|
|
expect($def->shortName)->toBe('v');
|
|
expect($def->description)->toBe('Enable verbose output');
|
|
});
|
|
|
|
it('creates email argument', function () {
|
|
$def = ArgumentDefinition::email('email', required: true, description: 'User email');
|
|
|
|
expect($def->name)->toBe('email');
|
|
expect($def->type)->toBe(ArgumentType::EMAIL);
|
|
expect($def->required)->toBeTrue();
|
|
expect($def->description)->toBe('User email');
|
|
});
|
|
|
|
it('creates integer argument', function () {
|
|
$def = ArgumentDefinition::integer('count', required: false, default: 10, description: 'Item count');
|
|
|
|
expect($def->name)->toBe('count');
|
|
expect($def->type)->toBe(ArgumentType::INTEGER);
|
|
expect($def->required)->toBeFalse();
|
|
expect($def->default)->toBe(10);
|
|
expect($def->description)->toBe('Item count');
|
|
});
|
|
|
|
it('creates choice argument', function () {
|
|
$def = ArgumentDefinition::choice('mode', ['dev', 'prod', 'test'], required: false, default: 'dev');
|
|
|
|
expect($def->name)->toBe('mode');
|
|
expect($def->type)->toBe(ArgumentType::STRING);
|
|
expect($def->allowedValues)->toBe(['dev', 'prod', 'test']);
|
|
expect($def->default)->toBe('dev');
|
|
});
|
|
|
|
it('throws exception for empty name', function () {
|
|
expect(fn () => new ArgumentDefinition('', ArgumentType::STRING))
|
|
->toThrow(\InvalidArgumentException::class, 'Argument name cannot be empty');
|
|
});
|
|
|
|
it('throws exception for invalid short name length', function () {
|
|
expect(fn () => new ArgumentDefinition('name', ArgumentType::STRING, shortName: 'ab'))
|
|
->toThrow(\InvalidArgumentException::class, 'Short name must be exactly one character');
|
|
});
|
|
|
|
it('throws exception when required argument has default', function () {
|
|
expect(fn () => new ArgumentDefinition('name', ArgumentType::STRING, required: true, default: 'Guest'))
|
|
->toThrow(\InvalidArgumentException::class, 'Required arguments cannot have default values');
|
|
});
|
|
|
|
it('throws exception when boolean argument has allowed values', function () {
|
|
expect(fn () => new ArgumentDefinition('flag', ArgumentType::BOOLEAN, allowedValues: ['true', 'false']))
|
|
->toThrow(\InvalidArgumentException::class, 'Boolean arguments cannot have allowed values');
|
|
});
|
|
|
|
it('gets display name with short name', function () {
|
|
$def = ArgumentDefinition::flag('verbose', 'v');
|
|
|
|
expect($def->getDisplayName())->toBe('v, verbose');
|
|
});
|
|
|
|
it('gets display name without short name', function () {
|
|
$def = ArgumentDefinition::required('name');
|
|
|
|
expect($def->getDisplayName())->toBe('name');
|
|
});
|
|
|
|
it('gets usage text for boolean flag', function () {
|
|
$def = ArgumentDefinition::flag('verbose', 'v');
|
|
|
|
expect($def->getUsageText())->toBe('[--verbose]');
|
|
});
|
|
|
|
it('gets usage text for required boolean flag', function () {
|
|
$def = new ArgumentDefinition('verbose', ArgumentType::BOOLEAN, required: true);
|
|
|
|
expect($def->getUsageText())->toBe('--verbose');
|
|
});
|
|
|
|
it('gets usage text for optional boolean flag', function () {
|
|
$def = ArgumentDefinition::flag('verbose', 'v');
|
|
|
|
expect($def->getUsageText())->toBe('[--verbose]');
|
|
});
|
|
|
|
it('gets usage text for required string argument', function () {
|
|
$def = ArgumentDefinition::required('name');
|
|
|
|
expect($def->getUsageText())->toContain('--name');
|
|
expect($def->getUsageText())->toContain('<');
|
|
});
|
|
|
|
it('gets usage text for optional string argument', function () {
|
|
$def = ArgumentDefinition::optional('name', 'Guest');
|
|
|
|
expect($def->getUsageText())->toContain('--name');
|
|
expect($def->getUsageText())->toContain('[');
|
|
});
|
|
|
|
it('gets usage text with allowed values', function () {
|
|
$def = ArgumentDefinition::choice('mode', ['dev', 'prod']);
|
|
|
|
expect($def->getUsageText())->toContain('dev|prod');
|
|
});
|
|
|
|
it('validates required value', function () {
|
|
$def = ArgumentDefinition::required('name');
|
|
|
|
expect(fn () => $def->validateValue(null))
|
|
->toThrow(\InvalidArgumentException::class, "Required argument 'name' is missing");
|
|
});
|
|
|
|
it('validates allowed values', function () {
|
|
$def = ArgumentDefinition::choice('mode', ['dev', 'prod', 'test']);
|
|
|
|
// Should not throw for valid value
|
|
$def->validateValue('dev');
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('throws exception for invalid allowed value', function () {
|
|
$def = ArgumentDefinition::choice('mode', ['dev', 'prod', 'test']);
|
|
|
|
expect(fn () => $def->validateValue('invalid'))
|
|
->toThrow(\InvalidArgumentException::class, "Invalid value 'invalid'");
|
|
});
|
|
|
|
it('allows empty value for optional argument', function () {
|
|
$def = ArgumentDefinition::optional('name', 'Guest');
|
|
|
|
// Should not throw
|
|
$def->validateValue(null);
|
|
$def->validateValue('');
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('validates value with default', function () {
|
|
$def = ArgumentDefinition::optional('name', 'Guest');
|
|
|
|
// Should not throw
|
|
$def->validateValue('John');
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('creates argument with all properties', function () {
|
|
$def = new ArgumentDefinition(
|
|
name: 'test',
|
|
type: ArgumentType::INTEGER,
|
|
required: false,
|
|
default: 42,
|
|
description: 'Test description',
|
|
shortName: 't',
|
|
allowedValues: []
|
|
);
|
|
|
|
expect($def->name)->toBe('test');
|
|
expect($def->type)->toBe(ArgumentType::INTEGER);
|
|
expect($def->required)->toBeFalse();
|
|
expect($def->default)->toBe(42);
|
|
expect($def->description)->toBe('Test description');
|
|
expect($def->shortName)->toBe('t');
|
|
});
|
|
});
|
|
|