diff --git a/src/Concerns/FakesInputOutput.php b/src/Concerns/FakesInputOutput.php index e136db1d..19a697e5 100644 --- a/src/Concerns/FakesInputOutput.php +++ b/src/Concerns/FakesInputOutput.php @@ -2,6 +2,7 @@ namespace Laravel\Prompts\Concerns; +use Closure; use Laravel\Prompts\Output\BufferedConsoleOutput; use Laravel\Prompts\Terminal; use PHPUnit\Framework\Assert; @@ -29,13 +30,25 @@ public static function fake(array $keys = []): void $mock->shouldReceive('lines')->byDefault()->andReturn(24); $mock->shouldReceive('initDimensions')->byDefault(); - foreach ($keys as $key) { + static::fakeKeyPresses($keys, function (string $key) use ($mock) { $mock->shouldReceive('read')->once()->andReturn($key); - } + }); static::$terminal = $mock; - self::setOutput(new BufferedConsoleOutput()); + static::setOutput(new BufferedConsoleOutput()); + } + + /** + * Implementation of the looping mechanism for simulating key presses. + * + * @param array $keys + */ + public static function fakeKeyPresses(array $keys, Closure $closure): void + { + foreach ($keys as $key) { + $closure($key); + } } /** diff --git a/src/Prompt.php b/src/Prompt.php index 1d0709c9..cf416995 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -5,6 +5,7 @@ use Closure; use Laravel\Prompts\Exceptions\FormRevertedException; use Laravel\Prompts\Output\ConsoleOutput; +use Laravel\Prompts\Support\Nothing; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -122,7 +123,7 @@ public function prompt(): mixed $this->hideCursor(); $this->render(); - while (($key = static::terminal()->read()) !== null) { + $result = $this->runLoop(function (string $key): mixed { $continue = $this->handleKeyPress($key); $this->render(); @@ -142,12 +143,38 @@ public function prompt(): mixed return $this->value(); } - } + + // `null` is a valid return value for this loop + // so we'll return an instance of Nothing to + // indicate that the loop should continue. + return new Nothing; + }); + + return $result; } finally { $this->clearListeners(); } } + public function runLoop(callable $callable): mixed + { + while(($key = static::terminal()->read()) !== null) { + $result = $callable($key); + + if (! $this->is_nothing($result)) { + return $result; + } + } + } + + /** + * Check if the provided item is an instance of Nothing. + */ + public function is_nothing(mixed $item): bool + { + return is_object($item) && is_a($item, Nothing::class); + } + /** * Register a callback to be invoked when a user cancels a prompt. */ @@ -179,7 +206,7 @@ protected function capturePreviousNewLines(): void */ public static function setOutput(OutputInterface $output): void { - self::$output = $output; + static::$output = $output; } /** @@ -187,7 +214,7 @@ public static function setOutput(OutputInterface $output): void */ protected static function output(): OutputInterface { - return self::$output ??= new ConsoleOutput(); + return static::$output ??= new ConsoleOutput(); } /** diff --git a/src/Support/Nothing.php b/src/Support/Nothing.php new file mode 100644 index 00000000..8601ba7e --- /dev/null +++ b/src/Support/Nothing.php @@ -0,0 +1,14 @@ +