From e6f70bc0aaabd0820d6c424d32e61e80cafb0774 Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Fri, 12 Apr 2024 21:52:37 +1000 Subject: [PATCH] Fix multisearch list handling (#132) --- src/MultiSearchPrompt.php | 38 +++- .../Default/MultiSearchPromptRenderer.php | 4 +- tests/Feature/MultiSearchPromptTest.php | 165 +++++++++++++++--- 3 files changed, 175 insertions(+), 32 deletions(-) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index 4ba87ce9..86ea336c 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -17,6 +17,11 @@ class MultiSearchPrompt extends Prompt */ protected ?array $matches = null; + /** + * Whether the matches are initially a list. + */ + protected bool $isList; + /** * The selected values. * @@ -96,16 +101,25 @@ public function matches(): array return $this->matches; } - if (strlen($this->typedValue) === 0) { - $matches = ($this->options)($this->typedValue); + $matches = ($this->options)($this->typedValue); + + if (! isset($this->isList) && count($matches) > 0) { + // This needs to be captured the first time we receive matches so + // we know what we're dealing with later if matches is empty. + $this->isList = array_is_list($matches); + } + + if (! isset($this->isList)) { + return $this->matches = []; + } - return $this->matches = [ - ...array_diff($this->values, $matches), - ...$matches, - ]; + if (strlen($this->typedValue) > 0) { + return $this->matches = $matches; } - return $this->matches = ($this->options)($this->typedValue); + return $this->matches = $this->isList + ? [...array_diff(array_values($this->values), $matches), ...$matches] + : array_diff($this->values, $matches) + $matches; } /** @@ -123,7 +137,7 @@ public function visible(): array */ protected function toggleHighlighted(): void { - if (array_is_list($this->matches)) { + if ($this->isList()) { $label = $this->matches[$this->highlighted]; $key = $label; } else { @@ -165,4 +179,12 @@ public function labels(): array { return array_values($this->values); } + + /** + * Whether the matches are initially a list. + */ + public function isList(): bool + { + return $this->isList; + } } diff --git a/src/Themes/Default/MultiSearchPromptRenderer.php b/src/Themes/Default/MultiSearchPromptRenderer.php index 3eca3339..e27c2107 100644 --- a/src/Themes/Default/MultiSearchPromptRenderer.php +++ b/src/Themes/Default/MultiSearchPromptRenderer.php @@ -115,7 +115,7 @@ protected function renderOptions(MultiSearchPrompt $prompt): string ->map(function ($label, $key) use ($prompt) { $index = array_search($key, array_keys($prompt->matches())); $active = $index === $prompt->highlighted; - $selected = array_is_list($prompt->visible()) + $selected = $prompt->isList() ? in_array($label, $prompt->value()) : in_array($key, $prompt->value()); @@ -156,7 +156,7 @@ protected function getInfoText(MultiSearchPrompt $prompt): string $info = count($prompt->value()).' selected'; $hiddenCount = count($prompt->value()) - collect($prompt->matches()) - ->filter(fn ($label, $key) => in_array(array_is_list($prompt->matches()) ? $label : $key, $prompt->value())) + ->filter(fn ($label, $key) => in_array($prompt->isList() ? $label : $key, $prompt->value())) ->count(); if ($hiddenCount > 0) { diff --git a/tests/Feature/MultiSearchPromptTest.php b/tests/Feature/MultiSearchPromptTest.php index 94bdcf60..d23019e2 100644 --- a/tests/Feature/MultiSearchPromptTest.php +++ b/tests/Feature/MultiSearchPromptTest.php @@ -8,12 +8,13 @@ it('supports default results', function ($options, $expected) { Prompt::fake([ - Key::DOWN, // Highlight "Red" + Key::UP, // Highlight "Violet" + Key::SPACE, // Select "Violet" + 'G', // Search for "Green" + 'r', // Search for "Green" Key::DOWN, // Highlight "Green" Key::SPACE, // Select "Green" - 'B', // Search for "Blue" - Key::DOWN, // Highlight "Blue" - Key::SPACE, // Select "Blue" + Key::BACKSPACE, // Clear search Key::BACKSPACE, // Clear search Key::ENTER, // Confirm selection ]); @@ -28,25 +29,78 @@ ┌ What are your favorite colors? ──────────────────────────────┐ │ Search... │ ├──────────────────────────────────────────────────────────────┤ - │ ◻ Red │ - │ ◻ Green │ - │ ◻ Blue │ + │ ◻ Red ┃ │ + │ ◻ Orange │ │ + │ ◻ Yellow │ │ + │ ◻ Green │ │ + │ ◻ Blue │ │ └────────────────────────────────────────────────── 0 selected ┘ OUTPUT); Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ │ Search... │ ├──────────────────────────────────────────────────────────────┤ - │ ◻ Red │ - │ ◼ Green │ - │ ◼ Blue │ + │ ◻ Yellow │ │ + │ ◻ Green │ │ + │ ◻ Blue │ │ + │ ◻ Indigo │ │ + │ › ◻ Violet ┃ │ + └────────────────────────────────────────────────── 0 selected ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Search... │ + ├──────────────────────────────────────────────────────────────┤ + │ ◻ Yellow │ │ + │ ◻ Green │ │ + │ ◻ Blue │ │ + │ ◻ Indigo │ │ + │ › ◼ Violet ┃ │ + └────────────────────────────────────────────────── 1 selected ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Gr │ + ├──────────────────────────────────────────────────────────────┤ + │ ◻ Green │ + └─────────────────────────────────────── 1 selected (1 hidden) ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Gr │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◻ Green │ + └─────────────────────────────────────── 1 selected (1 hidden) ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Gr │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◼ Green │ + └─────────────────────────────────────── 2 selected (1 hidden) ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Search... │ + ├──────────────────────────────────────────────────────────────┤ + │ ◻ Red ┃ │ + │ ◻ Orange │ │ + │ ◻ Yellow │ │ + │ ◼ Green │ │ + │ ◻ Blue │ │ └────────────────────────────────────────────────── 2 selected ┘ OUTPUT); Prompt::assertStrippedOutputContains(<<<'OUTPUT' ┌ What are your favorite colors? ──────────────────────────────┐ + │ Violet │ │ Green │ - │ Blue │ └──────────────────────────────────────────────────────────────┘ OUTPUT); @@ -55,33 +109,39 @@ 'associative' => [ fn ($value) => collect([ 'red' => 'Red', + 'orange' => 'Orange', + 'yellow' => 'Yellow', 'green' => 'Green', 'blue' => 'Blue', + 'indigo' => 'Indigo', + 'violet' => 'Violet', ])->when( strlen($value), fn ($colors) => $colors->filter(fn ($label) => str_contains(strtolower($label), strtolower($value))) )->all(), - ['green', 'blue'], + ['violet', 'green'], ], 'list' => [ - fn ($value) => collect(['Red', 'Green', 'Blue'])->when( + fn ($value) => collect(['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet'])->when( strlen($value), fn ($colors) => $colors->filter(fn ($label) => str_contains(strtolower($label), strtolower($value))) )->values()->all(), - ['Green', 'Blue'], + ['Violet', 'Green'], ], ]); it('supports no default results', function ($options, $expected) { Prompt::fake([ - 'B', // Search for "Blue" - Key::DOWN, // Highlight "Blue" - Key::SPACE, // Select "Blue" + 'V', // Search for "Violet" + Key::UP, // Highlight "Violet" + Key::SPACE, // Select "Violet" Key::BACKSPACE, // Clear search 'G', // Search for "Green" + 'r', // Search for "Green" Key::DOWN, // Highlight "Green" Key::SPACE, // Select "Green" Key::BACKSPACE, // Clear search + Key::BACKSPACE, // Clear search Key::ENTER, // Confirm selection ]); @@ -98,16 +158,73 @@ OUTPUT); Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ V │ + ├──────────────────────────────────────────────────────────────┤ + │ ◻ Violet │ + └────────────────────────────────────────────────── 0 selected ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ V │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◻ Violet │ + └────────────────────────────────────────────────── 0 selected ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ V │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◼ Violet │ + └────────────────────────────────────────────────── 1 selected ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Search... │ + ├──────────────────────────────────────────────────────────────┤ + │ ◼ Violet │ + └────────────────────────────────────────────────── 1 selected ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Gr │ + ├──────────────────────────────────────────────────────────────┤ + │ ◻ Green │ + └─────────────────────────────────────── 1 selected (1 hidden) ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Gr │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◻ Green │ + └─────────────────────────────────────── 1 selected (1 hidden) ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ + │ Gr │ + ├──────────────────────────────────────────────────────────────┤ + │ › ◼ Green │ + └─────────────────────────────────────── 2 selected (1 hidden) ┘ + OUTPUT); + + Prompt::assertStrippedOutputContains(<<<'OUTPUT' + ┌ What are your favorite colors? ──────────────────────────────┐ │ Search... │ ├──────────────────────────────────────────────────────────────┤ - │ ◼ Blue │ + │ ◼ Violet │ │ ◼ Green │ └────────────────────────────────────────────────── 2 selected ┘ OUTPUT); Prompt::assertStrippedOutputContains(<<<'OUTPUT' ┌ What are your favorite colors? ──────────────────────────────┐ - │ Blue │ + │ Violet │ │ Green │ └──────────────────────────────────────────────────────────────┘ OUTPUT); @@ -117,17 +234,21 @@ 'associative' => [ fn ($value) => strlen($value) > 0 ? collect([ 'red' => 'Red', + 'orange' => 'Orange', + 'yellow' => 'Yellow', 'green' => 'Green', 'blue' => 'Blue', + 'indigo' => 'Indigo', + 'violet' => 'Violet', ])->filter(fn ($label) => str_contains(strtolower($label), strtolower($value)))->all() : [], - ['blue', 'green'], + ['violet', 'green'], ], 'list' => [ - fn ($value) => strlen($value) > 0 ? collect(['Red', 'Green', 'Blue']) + fn ($value) => strlen($value) > 0 ? collect(['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet']) ->filter(fn ($label) => str_contains(strtolower($label), strtolower($value))) ->values() ->all() : [], - ['Blue', 'Green'], + ['Violet', 'Green'], ], ]);