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