From c5741fee0fd1d94c2fb1b0c124397557a2bb0ecc Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 13 May 2018 22:44:21 +0200 Subject: [PATCH 1/3] Refactor split item item selection and CliMenu can select checks. Add tests for split item menu. --- src/CliMenu.php | 29 +++- src/MenuItem/SplitItem.php | 29 +++- test/CliMenuTest.php | 163 ++++++++++++++++++ test/MenuItem/SplitItemTest.php | 18 +- ...thNoSelectableItemsScrollingVertically.txt | 33 ++++ ...bleAndStaticItemsScrollingHorizontally.txt | 66 +++++++ ...itItemWithSelectableItemsScrollingLeft.txt | 66 +++++++ ...tItemWithSelectableItemsScrollingRight.txt | 66 +++++++ ...emWithSelectableItemsScrollingVertical.txt | 44 +++++ 9 files changed, 506 insertions(+), 8 deletions(-) create mode 100644 test/res/testSplitItemWithNoSelectableItemsScrollingVertically.txt create mode 100644 test/res/testSplitItemWithSelectableAndStaticItemsScrollingHorizontally.txt create mode 100644 test/res/testSplitItemWithSelectableItemsScrollingLeft.txt create mode 100644 test/res/testSplitItemWithSelectableItemsScrollingRight.txt create mode 100644 test/res/testSplitItemWithSelectableItemsScrollingVertical.txt diff --git a/src/CliMenu.php b/src/CliMenu.php index b2b1432e..ce3759b1 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -289,7 +289,7 @@ protected function moveSelectionVertically(string $direction) : void ? end($itemKeys) : reset($itemKeys); } - } while (!$this->getSelectedItem()->canSelect()); + } while (!$this->canSelect()); } /** @@ -301,6 +301,7 @@ protected function moveSelectionHorizontally(string $direction) : void return; } + /** @var SplitItem $item */ $item = $this->items[$this->selectedItem]; $itemKeys = array_keys($item->getItems()); $selectedItemIndex = $item->getSelectedItemIndex(); @@ -309,17 +310,37 @@ protected function moveSelectionHorizontally(string $direction) : void $direction === 'LEFT' ? $selectedItemIndex-- : $selectedItemIndex++; - $item->setSelectedItemIndex($selectedItemIndex); if (!array_key_exists($selectedItemIndex, $item->getItems())) { $selectedItemIndex = $direction === 'LEFT' ? end($itemKeys) : reset($itemKeys); - $item->setSelectedItemIndex($selectedItemIndex); } - } while (!$item->getSelectedItem()->canSelect()); + } while (!$item->canSelectIndex($selectedItemIndex)); + + $item->setSelectedItemIndex($selectedItemIndex); + } + + /** + * Can the currently selected item actually be selected? + * + * For example: + * selectable item -> no + * static item -> no + * split item with static items -> no + * split item with selectable item -> yes + * + * @return bool + */ + private function canSelect() : bool + { + return $this->items[$this->selectedItem]->canSelect(); } + /** + * Retrieve the item the user actually selected + * + */ public function getSelectedItem() : MenuItemInterface { $item = $this->items[$this->selectedItem]; diff --git a/src/MenuItem/SplitItem.php b/src/MenuItem/SplitItem.php index d48275c8..148e7787 100644 --- a/src/MenuItem/SplitItem.php +++ b/src/MenuItem/SplitItem.php @@ -200,6 +200,19 @@ private function buildCell( }, $content, array_keys($content)); } + /** + * Is there an item with this index and can it be + * selected? + */ + public function canSelectIndex(int $index) : bool + { + return isset($this->items[$index]) && $this->items[$index]->canSelect(); + } + + /** + * Set the item index which should be selected. If the item does + * not exist then throw an exception. + */ public function setSelectedItemIndex(int $index) : void { if (!isset($this->items[$index])) { @@ -209,16 +222,26 @@ public function setSelectedItemIndex(int $index) : void $this->selectedItemIndex = $index; } + /** + * Get the currently select item index. + * May be null in case of no selectable item. + */ public function getSelectedItemIndex() : ?int { return $this->selectedItemIndex; } + /** + * Get the currently selected item - if no items are selectable + * then throw an exception. + */ public function getSelectedItem() : MenuItemInterface { - return $this->selectedItemIndex !== null - ? $this->items[$this->selectedItemIndex] - : $this; + if (null === $this->selectedItemIndex) { + throw new \RuntimeException('No item is selected'); + } + + return $this->items[$this->selectedItemIndex]; } public function getItems() : array diff --git a/test/CliMenuTest.php b/test/CliMenuTest.php index 0bc271e1..b9e1bf60 100644 --- a/test/CliMenuTest.php +++ b/test/CliMenuTest.php @@ -6,6 +6,8 @@ use PhpSchool\CliMenu\Exception\MenuNotOpenException; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\SelectableItem; +use PhpSchool\CliMenu\MenuItem\SplitItem; +use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; use PhpSchool\Terminal\Terminal; use PhpSchool\Terminal\UnixTerminal; @@ -667,6 +669,167 @@ public function testRemoveCustomControlMapping() : void self::assertSame([], $this->readAttribute($menu, 'customControlMappings')); } + public function testSplitItemWithNoSelectableItemsScrollingVertically() : void + { + $this->terminal->expects($this->exactly(3)) + ->method('read') + ->willReturn("\033[B", "\033[B", "\n"); + + $action = function (CliMenu $menu) { + $menu->close(); + }; + + $menu = new CliMenu('PHP School FTW', [], $this->terminal); + $menu->addItem(new SelectableItem('One', $action)); + $menu->addItem(new SplitItem([new StaticItem('Two'), new StaticItem('Three')])); + $menu->addItem(new SelectableItem('Four', $action)); + + $menu->open(); + + self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); + } + + public function testSplitItemWithSelectableItemsScrollingVertical() : void + { + $this->terminal->expects($this->exactly(4)) + ->method('read') + ->willReturn("\033[B", "\033[B", "\033[B", "\n"); + + $action = function (CliMenu $menu) { + $menu->close(); + }; + + $splitAction = function (CliMenu $menu) { + }; + + $menu = new CliMenu('PHP School FTW', [], $this->terminal); + $menu->addItem(new SelectableItem('One', $action)); + $menu->addItem(new SplitItem( + [new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)]) + ); + $menu->addItem(new SelectableItem('Four', $action)); + + $menu->open(); + + self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); + } + + public function testSplitItemWithSelectableItemsScrollingRight() : void + { + $this->terminal->expects($this->exactly(6)) + ->method('read') + ->willReturn("\033[B", "\033[C", "\033[C", "\033[C", "\033[B", "\n"); + + $action = function (CliMenu $menu) { + $menu->close(); + }; + + $splitAction = function (CliMenu $menu) { + }; + + $menu = new CliMenu('PHP School FTW', [], $this->terminal); + $menu->addItem(new SelectableItem('One', $action)); + $menu->addItem(new SplitItem( + [new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)]) + ); + $menu->addItem(new SelectableItem('Four', $action)); + + $menu->open(); + + self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); + } + + public function testSplitItemWithSelectableItemsScrollingLeft() : void + { + $this->terminal->expects($this->exactly(6)) + ->method('read') + ->willReturn("\033[B", "\033[D", "\033[D", "\033[D", "\033[B", "\n"); + + $action = function (CliMenu $menu) { + $menu->close(); + }; + + $splitAction = function (CliMenu $menu) { + }; + + $menu = new CliMenu('PHP School FTW', [], $this->terminal); + $menu->addItem(new SelectableItem('One', $action)); + $menu->addItem( + new SplitItem( + [ + new SelectableItem('Two', $splitAction), + new SelectableItem('Three', $splitAction), + new SelectableItem('Four', $splitAction), + ] + ) + ); + $menu->addItem(new SelectableItem('Five', $action)); + + $menu->open(); + + self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); + } + + public function testSplitItemWithSelectableAndStaticItemsScrollingHorizontally() : void + { + $this->terminal->expects($this->exactly(6)) + ->method('read') + ->willReturn("\033[B", "\033[D", "\033[D", "\033[D", "\033[B", "\n"); + + $action = function (CliMenu $menu) { + $menu->close(); + }; + + $splitAction = function (CliMenu $menu) { + }; + + $menu = new CliMenu('PHP School FTW', [], $this->terminal); + $menu->addItem(new SelectableItem('One', $action)); + $menu->addItem( + new SplitItem( + [ + new SelectableItem('Two', $splitAction), + new StaticItem('Three'), + new SelectableItem('Four', $splitAction), + ] + ) + ); + $menu->addItem(new SelectableItem('Five', $action)); + + $menu->open(); + + self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch()); + } + + + public function testSelectableCallableReceivesSelectableAndNotSplitItem() : void + { + $this->terminal->expects($this->exactly(1)) + ->method('read') + ->willReturn("\n"); + + $actualSelectedItem = null; + $action = function (CliMenu $menu) use (&$actualSelectedItem) { + $actualSelectedItem = $menu->getSelectedItem(); + $menu->close(); + }; + + $expectedSelectedItem = new SelectableItem('Two', $action); + $menu = new CliMenu('PHP School FTW', [], $this->terminal); + $menu->addItem( + new SplitItem( + [ + $expectedSelectedItem, + new StaticItem('Three'), + new SelectableItem('Four', $action), + ] + ) + ); + $menu->open(); + + self::assertSame($expectedSelectedItem, $actualSelectedItem); + } + private function getTestFile() : string { return sprintf('%s/res/%s.txt', __DIR__, $this->getName()); diff --git a/test/MenuItem/SplitItemTest.php b/test/MenuItem/SplitItemTest.php index 313e6164..99b3aa8e 100644 --- a/test/MenuItem/SplitItemTest.php +++ b/test/MenuItem/SplitItemTest.php @@ -431,11 +431,27 @@ public function testGetSelectedItemReturnsItem() : void self::assertSame($item2, $splitItem->getSelectedItem()); } - public function testGetSelectedItemReturnsSplitItemWhenNoSelectableItemExists() : void + public function testGetSelectedItemThrowsExceptionWhenNoSelectableItemExists() : void { + self::expectException(\RuntimeException::class); + self::expectExceptionMessage('No item is selected'); + $item1 = new StaticItem('One'); $splitItem = new SplitItem([$item1]); self::assertSame($splitItem, $splitItem->getSelectedItem()); } + + public function testCanSelectIndex() : void + { + $item1 = new StaticItem('One'); + $item2 = new SelectableItem('Two', function () { + }); + + $splitItem = new SplitItem([$item1, $item2]); + + self::assertFalse($splitItem->canSelectIndex(0)); + self::assertFalse($splitItem->canSelectIndex(5)); + self::assertTrue($splitItem->canSelectIndex(1)); + } } diff --git a/test/res/testSplitItemWithNoSelectableItemsScrollingVertically.txt b/test/res/testSplitItemWithNoSelectableItemsScrollingVertically.txt new file mode 100644 index 00000000..a0c4054c --- /dev/null +++ b/test/res/testSplitItemWithNoSelectableItemsScrollingVertically.txt @@ -0,0 +1,33 @@ + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  Two Three  +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  Two Three  +  ● Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  Two Three  +  ○ Four  +   + + diff --git a/test/res/testSplitItemWithSelectableAndStaticItemsScrollingHorizontally.txt b/test/res/testSplitItemWithSelectableAndStaticItemsScrollingHorizontally.txt new file mode 100644 index 00000000..a428a263 --- /dev/null +++ b/test/res/testSplitItemWithSelectableAndStaticItemsScrollingHorizontally.txt @@ -0,0 +1,66 @@ + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  ○ Two Three ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  Three ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two Three ● Four   +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  Three ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two Three ● Four   +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two Three ○ Four  +  ● Five  +   + + diff --git a/test/res/testSplitItemWithSelectableItemsScrollingLeft.txt b/test/res/testSplitItemWithSelectableItemsScrollingLeft.txt new file mode 100644 index 00000000..80db77a7 --- /dev/null +++ b/test/res/testSplitItemWithSelectableItemsScrollingLeft.txt @@ -0,0 +1,66 @@ + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  ○ Two ○ Three ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  ○ Three ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ○ Three ● Four   +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ● Three  ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  ○ Three ○ Four  +  ○ Five  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ○ Three ○ Four  +  ● Five  +   + + diff --git a/test/res/testSplitItemWithSelectableItemsScrollingRight.txt b/test/res/testSplitItemWithSelectableItemsScrollingRight.txt new file mode 100644 index 00000000..e5bc3961 --- /dev/null +++ b/test/res/testSplitItemWithSelectableItemsScrollingRight.txt @@ -0,0 +1,66 @@ + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  ○ Two ○ Three  +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  ○ Three  +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ● Three   +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  ○ Three  +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ● Three   +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ○ Three  +  ● Four  +   + + diff --git a/test/res/testSplitItemWithSelectableItemsScrollingVertical.txt b/test/res/testSplitItemWithSelectableItemsScrollingVertical.txt new file mode 100644 index 00000000..f68eff09 --- /dev/null +++ b/test/res/testSplitItemWithSelectableItemsScrollingVertical.txt @@ -0,0 +1,44 @@ + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  ○ Two ○ Three  +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ● Two  ○ Three  +  ○ Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ○ One  +  ○ Two ○ Three  +  ● Four  +   + + + + +   +  PHP School FTW  +  ==========================================  +  ● One  +  ○ Two ○ Three  +  ○ Four  +   + + From ecb429d036268183fc0d63ae350c6ee90c64d6be Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 13 May 2018 22:46:36 +0200 Subject: [PATCH 2/3] Fix doc --- src/CliMenu.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CliMenu.php b/src/CliMenu.php index ce3759b1..376f4de6 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -325,10 +325,10 @@ protected function moveSelectionHorizontally(string $direction) : void * Can the currently selected item actually be selected? * * For example: - * selectable item -> no + * selectable item -> yes * static item -> no - * split item with static items -> no - * split item with selectable item -> yes + * split item with only static items -> no + * split item with at least one selectable item -> yes * * @return bool */ From d2938340b356f4740782848cbf3b91ff46e18caa Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 13 May 2018 22:48:22 +0200 Subject: [PATCH 3/3] CS --- src/CliMenu.php | 6 +++--- test/CliMenuTest.php | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/CliMenu.php b/src/CliMenu.php index 376f4de6..e08cfa1c 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -323,13 +323,13 @@ protected function moveSelectionHorizontally(string $direction) : void /** * Can the currently selected item actually be selected? - * + * * For example: * selectable item -> yes * static item -> no * split item with only static items -> no * split item with at least one selectable item -> yes - * + * * @return bool */ private function canSelect() : bool @@ -339,7 +339,7 @@ private function canSelect() : bool /** * Retrieve the item the user actually selected - * + * */ public function getSelectedItem() : MenuItemInterface { diff --git a/test/CliMenuTest.php b/test/CliMenuTest.php index b9e1bf60..48a14568 100644 --- a/test/CliMenuTest.php +++ b/test/CliMenuTest.php @@ -704,8 +704,10 @@ public function testSplitItemWithSelectableItemsScrollingVertical() : void $menu = new CliMenu('PHP School FTW', [], $this->terminal); $menu->addItem(new SelectableItem('One', $action)); - $menu->addItem(new SplitItem( - [new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)]) + $menu->addItem( + new SplitItem( + [new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)] + ) ); $menu->addItem(new SelectableItem('Four', $action)); @@ -729,8 +731,10 @@ public function testSplitItemWithSelectableItemsScrollingRight() : void $menu = new CliMenu('PHP School FTW', [], $this->terminal); $menu->addItem(new SelectableItem('One', $action)); - $menu->addItem(new SplitItem( - [new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)]) + $menu->addItem( + new SplitItem( + [new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)] + ) ); $menu->addItem(new SelectableItem('Four', $action));