Skip to content

Commit f65d938

Browse files
committed
Merge branch 'release/0.8.9'
2 parents 2dd5bac + 12ff492 commit f65d938

28 files changed

Lines changed: 3846 additions & 89 deletions

.github/workflows/ci.yml

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,29 @@ on: [push]
55
jobs:
66
build-test:
77
runs-on: ubuntu-latest
8-
container:
9-
image: php:8.4 # This forces the job to run in a Docker container
108

119
steps:
1210
- name: Checkout
1311
uses: actions/checkout@v3
1412

15-
- name: Install System Dependencies (Git, Zip, Unzip)
16-
run: |
17-
apt-get update
18-
apt-get install -y unzip git zip
19-
20-
- name: Install and Enable extensions
21-
run: |
22-
docker-php-ext-install sockets calendar
23-
docker-php-ext-enable sockets calendar
24-
25-
- name: Install Composer
26-
run: |
27-
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
28-
composer --version
13+
- name: Setup PHP
14+
uses: shivammathur/setup-php@v2
15+
with:
16+
php-version: 8.4
17+
extensions: sockets, calendar, pcov
18+
coverage: pcov
2919

3020
- name: Install Dependencies
31-
run: composer install --prefer-dist --no-progress
32-
33-
- name: Run PHPUnit
34-
run: vendor/bin/phpunit tests
21+
run: composer install --prefer-dist --no-progress --no-interaction --optimize-autoloader
22+
23+
- name: Run PHPUnit with Coverage
24+
run: vendor/bin/phpunit tests --coverage-clover coverage.xml --coverage-filter src
25+
26+
- name: Upload coverage to Codecov
27+
uses: codecov/codecov-action@v5
28+
with:
29+
token: ${{ secrets.CODECOV_TOKEN }}
30+
files: ./coverage.xml
31+
flags: cli
32+
slug: Neuron-PHP/cli
33+
fail_ci_if_error: true

.version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"strategy": "semver",
33
"major": 0,
44
"minor": 8,
5-
"patch": 8,
5+
"patch": 9,
66
"build": 0
77
}

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
},
2626
"autoload-dev": {
2727
"psr-4": {
28-
"Tests\\": "tests/"
28+
"Tests\\": "tests/",
29+
"Neuron\\Core\\": "../core/src/Core/"
2930
}
3031
},
3132
"bin": [

readme.md

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[![CI](https://github.com/Neuron-PHP/cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Neuron-PHP/cli/actions)
2+
[![codecov](https://codecov.io/gh/Neuron-PHP/cli/branch/develop/graph/badge.svg)](https://codecov.io/gh/Neuron-PHP/cli)
23
# Neuron CLI Component
34

45
A unified command-line interface for the Neuron PHP framework that provides a modern, extensible CLI tool for all Neuron components.
@@ -473,6 +474,10 @@ public function execute(): int
473474

474475
## Testing Your Commands
475476

477+
The CLI component provides comprehensive testing support, including the ability to test commands that require user input.
478+
479+
### Testing Commands Without User Input
480+
476481
```php
477482
use PHPUnit\Framework\TestCase;
478483
use Neuron\Cli\Console\Input;
@@ -483,23 +488,155 @@ class MakeControllerCommandTest extends TestCase
483488
public function testExecute(): void
484489
{
485490
$command = new MakeControllerCommand();
486-
491+
487492
// Mock input
488493
$input = new Input(['UserController', '--resource']);
489494
$output = new Output(false); // No colors for testing
490-
495+
491496
$command->setInput($input);
492497
$command->setOutput($output);
493498
$command->configure();
494499
$input->parse($command);
495-
500+
496501
$exitCode = $command->execute();
497-
502+
498503
$this->assertEquals(0, $exitCode);
499504
}
500505
}
501506
```
502507

508+
### Testing Commands With User Input
509+
510+
Commands that use `prompt()`, `confirm()`, `secret()`, or `choice()` can be tested using the `TestInputReader`:
511+
512+
```php
513+
use PHPUnit\Framework\TestCase;
514+
use Neuron\Cli\Console\Input;
515+
use Neuron\Cli\Console\Output;
516+
use Neuron\Cli\IO\TestInputReader;
517+
518+
class SetupCommandTest extends TestCase
519+
{
520+
public function testInteractiveSetup(): void
521+
{
522+
$command = new SetupCommand();
523+
524+
// Create test input reader with pre-programmed responses
525+
$inputReader = new TestInputReader();
526+
$inputReader->addResponse('my-project'); // Project name
527+
$inputReader->addResponse('John Doe'); // Author name
528+
$inputReader->addResponse('john@example.com'); // Email
529+
$inputReader->addResponse('yes'); // Confirmation
530+
531+
// Configure command
532+
$input = new Input([]);
533+
$output = new Output(false);
534+
535+
$command->setInput($input);
536+
$command->setOutput($output);
537+
$command->setInputReader($inputReader);
538+
539+
// Execute command
540+
$exitCode = $command->execute();
541+
542+
// Assertions
543+
$this->assertEquals(0, $exitCode);
544+
545+
// Verify the prompts that were shown
546+
$prompts = $inputReader->getPromptHistory();
547+
$this->assertCount(4, $prompts);
548+
$this->assertStringContainsString('Project name', $prompts[0]);
549+
$this->assertStringContainsString('Author name', $prompts[1]);
550+
}
551+
552+
public function testUserCancelsSetup(): void
553+
{
554+
$command = new SetupCommand();
555+
556+
// User will cancel the setup
557+
$inputReader = new TestInputReader();
558+
$inputReader->addResponses([
559+
'test-project',
560+
'Test User',
561+
'test@example.com',
562+
'no' // Cancel confirmation
563+
]);
564+
565+
$input = new Input([]);
566+
$output = new Output(false);
567+
568+
$command->setInput($input);
569+
$command->setOutput($output);
570+
$command->setInputReader($inputReader);
571+
572+
$exitCode = $command->execute();
573+
574+
// Should return non-zero exit code when cancelled
575+
$this->assertNotEquals(0, $exitCode);
576+
}
577+
}
578+
```
579+
580+
### Using Input Reader in Commands
581+
582+
To make your commands testable, use the convenience methods provided by the `Command` base class:
583+
584+
```php
585+
class SetupCommand extends Command
586+
{
587+
public function execute(): int
588+
{
589+
// Use built-in convenience methods instead of reading STDIN directly
590+
$name = $this->prompt('Enter project name: ');
591+
592+
if ($this->confirm('Enable caching?', true)) {
593+
// User confirmed
594+
}
595+
596+
$password = $this->secret('Enter password: ');
597+
598+
$env = $this->choice(
599+
'Select environment:',
600+
['development', 'staging', 'production'],
601+
'development'
602+
);
603+
604+
return 0;
605+
}
606+
}
607+
```
608+
609+
These convenience methods automatically use the injected `IInputReader`, making your commands fully testable without requiring actual user input.
610+
611+
### TestInputReader Features
612+
613+
The `TestInputReader` class provides:
614+
615+
- **Response Queue**: Pre-program multiple responses with `addResponse()` or `addResponses()`
616+
- **Prompt History**: Track all prompts shown with `getPromptHistory()`
617+
- **Automatic Validation**: Throws exceptions if responses run out, helping catch test bugs
618+
- **Fluent Interface**: Chain `addResponse()` calls for cleaner test setup
619+
- **Full Interface Support**: Implements all `IInputReader` methods (prompt, confirm, secret, choice)
620+
621+
```php
622+
$reader = new TestInputReader();
623+
$reader
624+
->addResponse('value1')
625+
->addResponse('value2')
626+
->addResponse('yes');
627+
628+
// Check if there are responses remaining
629+
if ($reader->hasMoreResponses()) {
630+
$count = $reader->getRemainingResponseCount();
631+
}
632+
633+
// Get history of prompts
634+
$prompts = $reader->getPromptHistory();
635+
636+
// Reset for reuse
637+
$reader->reset();
638+
```
639+
503640
## Contributing
504641

505642
When adding new features to the CLI component:

0 commit comments

Comments
 (0)