count())->toBe(0); expect($list->isEmpty())->toBeTrue(); }); it('creates command list with commands', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $list = new CommandList($command1, $command2); expect($list->count())->toBe(2); expect($list->has('test:command1'))->toBeTrue(); expect($list->has('test:command2'))->toBeTrue(); }); it('throws exception for duplicate command names', function () { $command1 = new ConsoleCommand('test:command', 'Description 1'); $command2 = new ConsoleCommand('test:command', 'Description 2'); expect(fn () => new CommandList($command1, $command2)) ->toThrow(DuplicateCommandException::class); }); it('adds command to list', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $list = CommandList::empty(); $list = $list->add($command1); $list = $list->add($command2); expect($list->count())->toBe(2); expect($list->has('test:command1'))->toBeTrue(); expect($list->has('test:command2'))->toBeTrue(); }); it('throws exception when adding duplicate command', function () { $command1 = new ConsoleCommand('test:command', 'Description 1'); $command2 = new ConsoleCommand('test:command', 'Description 2'); $list = new CommandList($command1); expect(fn () => $list->add($command2)) ->toThrow(DuplicateCommandException::class); }); it('gets command by name', function () { $command = new ConsoleCommand('test:command', 'Description'); $list = new CommandList($command); $retrieved = $list->get('test:command'); expect($retrieved)->toBe($command); expect($retrieved->name)->toBe('test:command'); expect($retrieved->description)->toBe('Description'); }); it('throws exception when getting non-existent command', function () { $list = CommandList::empty(); expect(fn () => $list->get('nonexistent:command')) ->toThrow(CommandNotFoundException::class); }); it('returns all command names', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $command3 = new ConsoleCommand('test:command3', 'Description 3'); $list = new CommandList($command1, $command2, $command3); $names = $list->getNames(); expect($names)->toHaveCount(3); expect($names)->toContain('test:command1'); expect($names)->toContain('test:command2'); expect($names)->toContain('test:command3'); }); it('finds similar commands using Levenshtein distance', function () { $command1 = new ConsoleCommand('test:hello', 'Description'); $command2 = new ConsoleCommand('test:help', 'Description'); $command3 = new ConsoleCommand('test:world', 'Description'); $command4 = new ConsoleCommand('test:hell', 'Description'); $list = new CommandList($command1, $command2, $command3, $command4); // 'test:helo' is similar to 'test:hello' (distance 1) $similar = $list->findSimilar('test:helo', 2); expect($similar)->toContain('test:hello'); expect($similar)->toContain('test:help'); expect($similar)->toContain('test:hell'); }); it('returns empty array when no similar commands found', function () { $command = new ConsoleCommand('test:command', 'Description'); $list = new CommandList($command); $similar = $list->findSimilar('completely:different', 3); expect($similar)->toBeEmpty(); }); it('respects max distance parameter', function () { $command1 = new ConsoleCommand('test:hello', 'Description'); $command2 = new ConsoleCommand('test:world', 'Description'); $list = new CommandList($command1, $command2); // 'test:helo' has distance 1 from 'test:hello', but distance 4 from 'test:world' $similar = $list->findSimilar('test:helo', 1); expect($similar)->toContain('test:hello'); expect($similar)->not->toContain('test:world'); }); it('does not include exact match in similar results', function () { $command = new ConsoleCommand('test:command', 'Description'); $list = new CommandList($command); $similar = $list->findSimilar('test:command', 3); // Exact match should not be included (distance 0) expect($similar)->toBeEmpty(); }); it('implements IteratorAggregate', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $list = new CommandList($command1, $command2); $commands = []; foreach ($list as $name => $command) { $commands[$name] = $command; } expect($commands)->toHaveCount(2); expect($commands['test:command1'])->toBe($command1); expect($commands['test:command2'])->toBe($command2); }); it('implements Countable', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $list = new CommandList($command1, $command2); expect(count($list))->toBe(2); }); it('converts to array', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $list = new CommandList($command1, $command2); $array = $list->toArray(); expect($array)->toHaveCount(2); expect($array)->toHaveKey('test:command1'); expect($array)->toHaveKey('test:command2'); }); it('returns all commands as array', function () { $command1 = new ConsoleCommand('test:command1', 'Description 1'); $command2 = new ConsoleCommand('test:command2', 'Description 2'); $list = new CommandList($command1, $command2); $allCommands = $list->getAllCommands(); expect($allCommands)->toHaveCount(2); expect($allCommands)->toContain($command1); expect($allCommands)->toContain($command2); }); });