diff --git a/README.md b/README.md index f4b98645..db2b1ed4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ * [Items](#appearance) * [Selectable Item](#selectable-item) * [Checkable Item](#checkable-item) + * [Radio Item](#radio-item) * [Line Break Item](#line-break-item) * [Static Item](#static-item) * [Ascii Art Item](#ascii-art-item) @@ -501,6 +502,7 @@ There a few different types of items you can add to your menu * Selectable Item - This is the type of item you need for something to be selectable (you can hit enter and it will invoke your callable) * Checkable Item - This is a checkbox type of item that keeps track of its toggled state to show a different marker. +* Radio Item - This is a radio type of item that keeps track of its toggled state to show a different marker. Disables all other radios within its `CliMenu` level. * Line Break Item - This is used to break up areas, it can span multiple lines and will be the width of Menu. Whatever string is passed will be repeated. * Static Item - This will print whatever text is passed, useful for headings. * Ascii Art Item - Special item which allows usage of Ascii art. It takes care of padding and alignment. @@ -568,6 +570,28 @@ $menu = (new CliMenuBuilder) When selecting an item, it will be toggled. Notice at first each item is unchecked. After selecting one it will become checked. +### Radio Item + +```php +getSelectedItem()->getText(); +}; + +$menu = (new CliMenuBuilder) + ->addRadioItem('Item 1', $callable) + ->addRadioItem('Item 2', $callable) + ->addRadioItem('Item 3', $callable) + ->build(); +``` + +When selecting an item, it will be toggled. Notice at first each item is unchecked. After selecting one it will become +checked and all other `RadioItem` within the same level will be unchecked. + ### Line Break Item ```php @@ -698,7 +722,7 @@ Split Items allows you to add multiple items on the same row. The full width of You can set the number of spaces separating items using `->setGutter()` (defaults to 2). -Only Selectable, Checkable, Static and SubMenu items are currently allowed inside a Split Item. +Only Selectable, Checkable, Radio, Static and SubMenu items are currently allowed inside a Split Item. ```php open(); There are a few things to note about the syntax and builder process here: 1. The first parameter to `addSplitItem` is a closure, which will be invoked with a new instance of `SplitItemBuilder` which you can use to add items to the split item. -2. You can call `addItem`, `addCheckableItem`, `addSubMenu` and `addStaticItem` on the `SplitItemBuilder`. +2. You can call `addItem`, `addCheckableItem`, `addRadioItem`, `addSubMenu` and `addStaticItem` on the `SplitItemBuilder`. 3. `SplitItemBuilder` has a fluent interface so you can chain method calls. Note: The closure used to build the split item is also binded with the `SplitItemBuilder` instance so you can add items and such using `$this->addItem()` rather than using the function @@ -806,6 +830,19 @@ $menu = (new CliMenuBuilder) ->build(); ``` +and for `\PhpSchool\CliMenu\MenuItem\RadioItem`: + +```php +setUnradioMarker('[ ] ') + ->setRadioMarker('[✔] ') + ->build(); +``` + ### Item Extra You can optionally display some arbitrary text on the right hand side of an item. You can customise this text and diff --git a/examples/radio-item.php b/examples/radio-item.php new file mode 100644 index 00000000..d3b7f0c9 --- /dev/null +++ b/examples/radio-item.php @@ -0,0 +1,35 @@ +getSelectedItem()->getText(); +}; + +$menu = (new CliMenuBuilder) + ->setTitle('Select a Language') + ->addSubMenu('Compiled', function (CliMenuBuilder $b) use ($itemCallable) { + $b->setTitle('Compiled Languages') + ->addRadioItem('Rust', $itemCallable) + ->addRadioItem('C++', $itemCallable) + ->addRadioItem('Go', $itemCallable) + ->addRadioItem('Java', $itemCallable) + ->addRadioItem('C', $itemCallable) + ; + }) + ->addSubMenu('Interpreted', function (CliMenuBuilder $b) use ($itemCallable) { + $b->setTitle('Interpreted Languages') + ->setUnradioMarker('[ ] ') + ->setRadioMarker('[✔] ') + ->addRadioItem('PHP', $itemCallable) + ->addRadioItem('Javascript', $itemCallable) + ->addRadioItem('Ruby', $itemCallable) + ->addRadioItem('Python', $itemCallable) + ; + }) + ->build(); + +$menu->open(); diff --git a/examples/split-checkable-item.php b/examples/split-checkable-item.php index 862c4beb..00638f4b 100644 --- a/examples/split-checkable-item.php +++ b/examples/split-checkable-item.php @@ -3,17 +3,11 @@ use PhpSchool\CliMenu\Builder\SplitItemBuilder; use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\Builder\CliMenuBuilder; -use PhpSchool\CliMenu\MenuItem\CheckableItem; require_once(__DIR__ . '/../vendor/autoload.php'); $itemCallable = function (CliMenu $menu) { - /** @var CheckableItem $item */ - $item = $menu->getSelectedItem(); - - $item->toggle(); - - $menu->redraw(); + echo $menu->getSelectedItem()->getText(); }; $menu = (new CliMenuBuilder) diff --git a/examples/split-radio-item.php b/examples/split-radio-item.php new file mode 100644 index 00000000..faf0224b --- /dev/null +++ b/examples/split-radio-item.php @@ -0,0 +1,26 @@ +getSelectedItem()->getText(); +}; + +$menu = (new CliMenuBuilder) + ->setTitle('Select a Language') + ->addSplitItem(function (SplitItemBuilder $b) use ($itemCallable) { + $b->setGutter(5) + ->addRadioItem('Rust', $itemCallable) + ->addRadioItem('C++', $itemCallable) + ->addRadioItem('Go', $itemCallable) + ->addRadioItem('Java', $itemCallable) + ->addRadioItem('C', $itemCallable) + ; + }) + ->build(); + +$menu->open(); diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index a821ae42..cf29d451 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -10,6 +10,7 @@ use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuItemInterface; use PhpSchool\CliMenu\MenuItem\MenuMenuItem; +use PhpSchool\CliMenu\MenuItem\RadioItem; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuItem\SplitItem; @@ -142,6 +143,17 @@ public function addCheckableItem( return $this; } + public function addRadioItem( + string $text, + callable $itemCallable, + bool $showItemExtra = false, + bool $disabled = false + ) : self { + $this->addMenuItem(new RadioItem($text, $itemCallable, $showItemExtra, $disabled)); + + return $this; + } + public function addStaticItem(string $text) : self { $this->addMenuItem(new StaticItem($text)); @@ -421,6 +433,20 @@ public function setCheckedMarker(string $marker) : self return $this; } + public function setUnradioMarker(string $marker) : self + { + $this->style->setUnradioMarker($marker); + + return $this; + } + + public function setRadioMarker(string $marker) : self + { + $this->style->setRadioMarker($marker); + + return $this; + } + public function setItemExtra(string $extra) : self { $this->style->setItemExtra($extra); diff --git a/src/Builder/SplitItemBuilder.php b/src/Builder/SplitItemBuilder.php index 99014b61..6ee60f31 100644 --- a/src/Builder/SplitItemBuilder.php +++ b/src/Builder/SplitItemBuilder.php @@ -6,6 +6,7 @@ use PhpSchool\CliMenu\MenuItem\CheckableItem; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuMenuItem; +use PhpSchool\CliMenu\MenuItem\RadioItem; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; @@ -69,6 +70,17 @@ public function addCheckableItem( return $this; } + public function addRadioItem( + string $text, + callable $itemCallable, + bool $showItemExtra = false, + bool $disabled = false + ) : self { + $this->splitItem->addItem(new RadioItem($text, $itemCallable, $showItemExtra, $disabled)); + + return $this; + } + public function addStaticItem(string $text) : self { $this->splitItem->addItem(new StaticItem($text)); diff --git a/src/CliMenu.php b/src/CliMenu.php index 3399a878..56174747 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -404,6 +404,24 @@ public function setSelectedItem(MenuItemInterface $item) : void $this->selectedItem = $key; } + public function getSelectedItemIndex() : int + { + if (null === $this->selectedItem) { + throw new \RuntimeException('No selected item'); + } + + return $this->selectedItem; + } + + public function getItemByIndex(int $index) : MenuItemInterface + { + if (!isset($this->items[$index])) { + throw new \RuntimeException('Item with index does not exist'); + } + + return $this->items[$index]; + } + public function executeAsSelected(MenuItemInterface $item) : void { $current = $this->items[$this->selectedItem]; diff --git a/src/MenuItem/CheckableItem.php b/src/MenuItem/CheckableItem.php index b0adbf1f..0973548a 100644 --- a/src/MenuItem/CheckableItem.php +++ b/src/MenuItem/CheckableItem.php @@ -3,11 +3,11 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\CliMenu; -use PhpSchool\CliMenu\MenuStyle; -use PhpSchool\CliMenu\Util\StringUtil; -class CheckableItem implements MenuItemInterface +class CheckableItem implements MenuItemInterface, ToggableItemInterface { + use ToggableTrait; + /** * @var callable */ @@ -28,11 +28,6 @@ class CheckableItem implements MenuItemInterface */ private $disabled = false; - /** - * @var bool - */ - private $checked = false; - public function __construct( string $text, callable $selectAction, @@ -74,43 +69,6 @@ public function setText(string $text) : void $this->text = $text; } - /** - * The output text for the item - * - * @param MenuStyle $style - * @param bool $selected Currently unused in this class - * @return array - */ - public function getRows(MenuStyle $style, bool $selected = false) : array - { - $marker = sprintf("%s", $this->checked ? $style->getCheckedMarker() : $style->getUncheckedMarker()); - - $length = $style->getDisplaysExtra() - ? $style->getContentWidth() - (mb_strlen($style->getItemExtra()) + 2) - : $style->getContentWidth(); - - $rows = explode( - "\n", - StringUtil::wordwrap( - sprintf('%s%s', $marker, $this->text), - $length, - sprintf("\n%s", str_repeat(' ', mb_strlen($marker))) - ) - ); - - return array_map(function ($row, $key) use ($style, $length) { - $text = $this->disabled ? $style->getDisabledItemText($row) : $row; - - if ($key === 0) { - return $this->showItemExtra - ? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $style->getItemExtra()) - : $text; - } - - return $text; - }, $rows, array_keys($rows)); - } - /** * Can the item be selected */ @@ -139,33 +97,4 @@ public function hideItemExtra() : void { $this->showItemExtra = false; } - - /** - * Toggles checked state - */ - public function toggle() - { - $this->checked = !$this->checked; - } - - /** - * Sets checked state to true - */ - public function setChecked() - { - $this->checked = true; - } - - /** - * Sets checked state to false - */ - public function setUnchecked() - { - $this->checked = false; - } - - public function getChecked(): bool - { - return $this->checked; - } } diff --git a/src/MenuItem/RadioItem.php b/src/MenuItem/RadioItem.php new file mode 100644 index 00000000..49f3b9f4 --- /dev/null +++ b/src/MenuItem/RadioItem.php @@ -0,0 +1,120 @@ +text = $text; + $this->selectAction = $selectAction; + $this->showItemExtra = $showItemExtra; + $this->disabled = $disabled; + } + + /** + * Execute the items callable if required + */ + public function getSelectAction() : ?callable + { + return function (CliMenu $cliMenu) { + $parentItem = $cliMenu->getItemByIndex($cliMenu->getSelectedItemIndex()); + + $siblings = $parentItem instanceof SplitItem + ? $parentItem->getItems() + : $cliMenu->getItems(); + + $filtered = array_filter( + $siblings, + function (MenuItemInterface $item) { + return $item instanceof self; + } + ); + + array_walk( + $filtered, + function (RadioItem $checkableItem) { + $checkableItem->setUnchecked(); + } + ); + + $this->setChecked(); + $cliMenu->redraw(); + + return ($this->selectAction)($cliMenu); + }; + } + + /** + * Return the raw string of text + */ + public function getText() : string + { + return $this->text; + } + + /** + * Set the raw string of text + */ + public function setText(string $text) : void + { + $this->text = $text; + } + + /** + * Can the item be selected + */ + public function canSelect() : bool + { + return !$this->disabled; + } + + public function showsItemExtra() : bool + { + return $this->showItemExtra; + } + + /** + * Enable showing item extra + */ + public function showItemExtra() : void + { + $this->showItemExtra = true; + } + + /** + * Disable showing item extra + */ + public function hideItemExtra() : void + { + $this->showItemExtra = false; + } +} diff --git a/src/MenuItem/SplitItem.php b/src/MenuItem/SplitItem.php index ea362cda..2808b333 100644 --- a/src/MenuItem/SplitItem.php +++ b/src/MenuItem/SplitItem.php @@ -134,6 +134,10 @@ public function getRows(MenuStyle $style, bool $selected = false) : array $markerType = $item->getChecked() ? $style->getCheckedMarker() : $style->getUncheckedMarker(); + } elseif ($item instanceof RadioItem) { + $markerType = $item->getChecked() + ? $style->getRadioMarker() + : $style->getUnradioMarker(); } else { $markerType = $style->getMarker($isSelected); } diff --git a/src/MenuItem/ToggableItemInterface.php b/src/MenuItem/ToggableItemInterface.php new file mode 100644 index 00000000..017484d7 --- /dev/null +++ b/src/MenuItem/ToggableItemInterface.php @@ -0,0 +1,28 @@ + $this instanceof CheckableItem + ? $style->getCheckedMarker() + : $style->getRadioMarker(), + false => $this instanceof CheckableItem + ? $style->getUncheckedMarker() + : $style->getUnradioMarker(), + ]; + + $marker = sprintf("%s", $markerTypes[$this->checked]); + + $length = $style->getDisplaysExtra() + ? $style->getContentWidth() - (mb_strlen($style->getItemExtra()) + 2) + : $style->getContentWidth(); + + $rows = explode( + "\n", + StringUtil::wordwrap( + sprintf('%s%s', $marker, $this->text), + $length, + sprintf("\n%s", str_repeat(' ', mb_strlen($marker))) + ) + ); + + return array_map(function ($row, $key) use ($style, $length) { + $text = $this->disabled ? $style->getDisabledItemText($row) : $row; + + if ($key === 0) { + return $this->showItemExtra + ? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $style->getItemExtra()) + : $text; + } + + return $text; + }, $rows, array_keys($rows)); + } + + /** + * Toggles checked state + */ + public function toggle() : void + { + $this->checked = !$this->checked; + } + + /** + * Sets checked state to true + */ + public function setChecked() : void + { + $this->checked = true; + } + + /** + * Sets checked state to false + */ + public function setUnchecked() : void + { + $this->checked = false; + } + + public function getChecked() : bool + { + return $this->checked; + } +} diff --git a/src/MenuStyle.php b/src/MenuStyle.php index f47eabdb..f2139bca 100644 --- a/src/MenuStyle.php +++ b/src/MenuStyle.php @@ -79,6 +79,16 @@ class MenuStyle */ private $uncheckedMarker; + /** + * @var string + */ + private $radioMarker; + + /** + * @var string + */ + private $unradioMarker; + /** * @var string */ @@ -170,6 +180,8 @@ class MenuStyle 'unselectedMarker' => '○ ', 'checkedMarker' => '[✔] ', 'uncheckedMarker' => '[ ] ', + 'radioMarker' => '[●] ', + 'unradioMarker' => '[○] ', 'itemExtra' => '✔', 'displaysExtra' => false, 'titleSeparator' => '=', @@ -243,6 +255,8 @@ public function __construct(Terminal $terminal = null) $this->setUnselectedMarker(self::$defaultStyleValues['unselectedMarker']); $this->setCheckedMarker(self::$defaultStyleValues['checkedMarker']); $this->setUncheckedMarker(self::$defaultStyleValues['uncheckedMarker']); + $this->setRadioMarker(self::$defaultStyleValues['radioMarker']); + $this->setUnradioMarker(self::$defaultStyleValues['unradioMarker']); $this->setItemExtra(self::$defaultStyleValues['itemExtra']); $this->setDisplaysExtra(self::$defaultStyleValues['displaysExtra']); $this->setTitleSeparator(self::$defaultStyleValues['titleSeparator']); @@ -266,6 +280,8 @@ public function hasChangedFromDefaults() : bool $this->unselectedMarker, $this->checkedMarker, $this->uncheckedMarker, + $this->radioMarker, + $this->unradioMarker, $this->itemExtra, $this->displaysExtra, $this->titleSeparator, @@ -597,6 +613,30 @@ public function setUncheckedMarker(string $marker) : self return $this; } + public function getRadioMarker() : string + { + return $this->radioMarker; + } + + public function setRadioMarker(string $marker) : self + { + $this->radioMarker = $marker; + + return $this; + } + + public function getUnradioMarker() : string + { + return $this->unradioMarker; + } + + public function setUnradioMarker(string $marker) : self + { + $this->unradioMarker = $marker; + + return $this; + } + public function setItemExtra(string $itemExtra) : self { $this->itemExtra = $itemExtra; diff --git a/test/Builder/CliMenuBuilderTest.php b/test/Builder/CliMenuBuilderTest.php index fd1fc879..1885f824 100644 --- a/test/Builder/CliMenuBuilderTest.php +++ b/test/Builder/CliMenuBuilderTest.php @@ -8,6 +8,7 @@ use PhpSchool\CliMenu\MenuItem\CheckableItem; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuMenuItem; +use PhpSchool\CliMenu\MenuItem\RadioItem; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\Terminal\Terminal; @@ -394,6 +395,31 @@ public function testAddCheckableItem() : void $this->checkMenuItems($menu, $expected); } + public function testAddRadioItem() : void + { + $callable = function () { + }; + + $builder = new CliMenuBuilder; + $builder->disableDefaultItems(); + $builder->addRadioItem('Item 1', $callable); + $builder->addRadioItem('Item 2', $callable); + $menu = $builder->build(); + + $expected = [ + [ + 'class' => RadioItem::class, + 'text' => 'Item 1', + ], + [ + 'class' => RadioItem::class, + 'text' => 'Item 2', + ], + ]; + + $this->checkMenuItems($menu, $expected); + } + public function testAddStaticItem() : void { diff --git a/test/Builder/SplitItemBuilderTest.php b/test/Builder/SplitItemBuilderTest.php index 3450c35f..c6b21b6a 100644 --- a/test/Builder/SplitItemBuilderTest.php +++ b/test/Builder/SplitItemBuilderTest.php @@ -6,6 +6,7 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuItem\CheckableItem; use PhpSchool\CliMenu\MenuItem\MenuMenuItem; +use PhpSchool\CliMenu\MenuItem\RadioItem; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; @@ -63,6 +64,31 @@ public function testAddCheckableItem() : void $this->checkItemItems($item, $expected); } + public function testAddRadioItem() : void + { + $callable = function () { + }; + + $menu = new CliMenu(null, []); + $builder = new SplitItemBuilder($menu); + $builder->addRadioItem('Item 1', $callable); + $builder->addRadioItem('Item 2', $callable); + $item = $builder->build(); + + $expected = [ + [ + 'class' => RadioItem::class, + 'text' => 'Item 1', + ], + [ + 'class' => RadioItem::class, + 'text' => 'Item 2', + ], + ]; + + $this->checkItemItems($item, $expected); + } + public function testAddStaticItem() : void { diff --git a/test/MenuItem/RadioItemTest.php b/test/MenuItem/RadioItemTest.php new file mode 100644 index 00000000..00f667df --- /dev/null +++ b/test/MenuItem/RadioItemTest.php @@ -0,0 +1,219 @@ +assertTrue($item->canSelect()); + } + + public function testGetSelectAction() : void + { + $callable = function () { + return 'callable is called'; + }; + $item = new RadioItem('Item', $callable); + + $cliMenu = $this->getMockBuilder(CLiMenu::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertSame($callable(), $item->getSelectAction()($cliMenu)); + } + + public function testShowsItemExtra() : void + { + $item = new RadioItem('Item', function () { + }); + $this->assertFalse($item->showsItemExtra()); + + $item = new RadioItem('Item', function () { + }, true); + $this->assertTrue($item->showsItemExtra()); + } + + public function testGetText() : void + { + $item = new RadioItem('Item', function () { + }); + $this->assertEquals('Item', $item->getText()); + } + + public function testGetRows() : void + { + $terminal = $this->createMock(Terminal::class); + $terminal->expects($this->any())->method('getWidth')->willReturn(100); + + $menuStyle = new MenuStyle($terminal); + $menuStyle->setPaddingLeftRight(0); + $menuStyle->setWidth(8); + + $item = new RadioItem('Item', function () { + }); + + $itemChecked = new RadioItem('Item', function () { + }); + $itemChecked->toggle(); + $this->assertEquals(['[○] Item'], $item->getRows($menuStyle)); + $this->assertEquals(['[○] Item'], $item->getRows($menuStyle, false)); + $this->assertEquals(['[●] Item'], $itemChecked->getRows($menuStyle, true)); + } + + public function testSetText() : void + { + $terminal = $this->createMock(Terminal::class); + $terminal->expects($this->any())->method('getWidth')->willReturn(100); + + $menuStyle = new MenuStyle($terminal); + $menuStyle->setPaddingLeftRight(0); + $menuStyle->setWidth(12); + + $item = new RadioItem('Item', function () { + }); + $item->setText('New Text'); + + $itemChecked = new RadioItem('Item', function () { + }); + $itemChecked->setText('New Text'); + $itemChecked->toggle(); + $this->assertEquals(['[○] New Text'], $item->getRows($menuStyle)); + $this->assertEquals(['[○] New Text'], $item->getRows($menuStyle, false)); + $this->assertEquals(['[●] New Text'], $itemChecked->getRows($menuStyle, true)); + } + + public function testTogglesMarker() : void + { + $terminal = $this->createMock(Terminal::class); + $terminal->expects($this->any())->method('getWidth')->willReturn(100); + + $menuStyle = new MenuStyle($terminal); + $menuStyle->setPaddingLeftRight(0); + $menuStyle->setWidth(12); + + $item = new RadioItem('Item', function () { + }); + + $itemChecked = new RadioItem('Item', function () { + }); + $itemChecked->toggle(); + $this->assertEquals(['[○] Item'], $item->getRows($menuStyle)); + $this->assertEquals(['[○] Item'], $item->getRows($menuStyle, false)); + $this->assertEquals(['[●] Item'], $itemChecked->getRows($menuStyle, true)); + + $itemChecked->toggle(); + + $this->assertEquals(['[○] Item'], $itemChecked->getRows($menuStyle, true)); + } + + public function testTogglesUnselectedMarkers() : void + { + $terminal = $this->createMock(Terminal::class); + $terminal->expects($this->any())->method('getWidth')->willReturn(100); + + $menuStyle = new MenuStyle($terminal); + $menuStyle->setPaddingLeftRight(0); + $menuStyle->setWidth(12); + + $item1 = new RadioItem('Item', function () { + }); + + $item2 = new RadioItem('Item', function () { + }); + + $item3 = new RadioItem('Item', function () { + }); + + $cliMenu = $this->getMockBuilder(CliMenu::class) + ->disableOriginalConstructor() + ->onlyMethods(['getItems', 'redraw', 'getSelectedItemIndex', 'getItemByIndex']) + ->getMock(); + + $cliMenu->expects($this->atLeastOnce()) + ->method('getSelectedItemIndex') + ->willReturn(1); + + $cliMenu->expects($this->atLeastOnce()) + ->method('getItemByIndex') + ->willReturn($item1); + + $cliMenu->expects($this->atLeastOnce()) + ->method('getItems') + ->willReturn([$item1, $item2, $item3]); + + $this->assertEquals(['[○] Item'], $item1->getRows($menuStyle)); + $this->assertEquals(['[○] Item'], $item2->getRows($menuStyle, false)); + $this->assertEquals(['[○] Item'], $item3->getRows($menuStyle, true)); + + $item1->getSelectAction()($cliMenu); + + $this->assertEquals(['[●] Item'], $item1->getRows($menuStyle)); + $this->assertEquals(['[○] Item'], $item2->getRows($menuStyle, false)); + $this->assertEquals(['[○] Item'], $item3->getRows($menuStyle, true)); + + $item3->getSelectAction()($cliMenu); + + $this->assertEquals(['[○] Item'], $item1->getRows($menuStyle)); + $this->assertEquals(['[○] Item'], $item2->getRows($menuStyle, false)); + $this->assertEquals(['[●] Item'], $item3->getRows($menuStyle, true)); + } + + public function testGetRowsWithItemExtra() : void + { + $terminal = $this->createMock(Terminal::class); + $terminal->expects($this->any())->method('getWidth')->willReturn(100); + + $menuStyle = new MenuStyle($terminal); + $menuStyle->setPaddingLeftRight(0); + $menuStyle->setWidth(20); + $menuStyle->setItemExtra('[EXTRA]'); + $menuStyle->setDisplaysExtra(true); + + $item = new RadioItem('Item', function () { + }, true); + $this->assertEquals(['[○] Item [EXTRA]'], $item->getRows($menuStyle)); + } + + public function testGetRowsWithMultipleLinesWithItemExtra() : void + { + $terminal = $this->createMock(Terminal::class); + $terminal->expects($this->any())->method('getWidth')->willReturn(100); + + $menuStyle = new MenuStyle($terminal); + $menuStyle->setPaddingLeftRight(0); + $menuStyle->setWidth(20); + $menuStyle->setItemExtra('[EXTRA]'); + $menuStyle->setDisplaysExtra(true); + + $item = new RadioItem('LONG ITEM LINE', function () { + }, true); + $this->assertEquals( + [ + "[○] LONG [EXTRA]", + " ITEM LINE", + ], + $item->getRows($menuStyle) + ); + } + + public function testHideAndShowItemExtra() : void + { + $item = new RadioItem('Item', function () { + }); + + $this->assertFalse($item->showsItemExtra()); + $item->showItemExtra(); + $this->assertTrue($item->showsItemExtra()); + $item->hideItemExtra(); + $this->assertFalse($item->showsItemExtra()); + } +} diff --git a/test/MenuItem/SplitItemTest.php b/test/MenuItem/SplitItemTest.php index 14908281..d97b1b1f 100644 --- a/test/MenuItem/SplitItemTest.php +++ b/test/MenuItem/SplitItemTest.php @@ -2,9 +2,12 @@ namespace PhpSchool\CliMenuTest\MenuItem; +use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuItem\AsciiArtItem; +use PhpSchool\CliMenu\MenuItem\CheckableItem; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuItemInterface; +use PhpSchool\CliMenu\MenuItem\RadioItem; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; @@ -466,4 +469,106 @@ public function testCanSelectIndex() : void self::assertFalse($splitItem->canSelectIndex(5)); self::assertTrue($splitItem->canSelectIndex(1)); } + + public function testCheckableItem() : void + { + $menuStyle = $this->createMock(MenuStyle::class); + + $menuStyle + ->expects($this->any()) + ->method('getContentWidth') + ->will($this->returnValue(30)); + + $menuStyle + ->expects($this->any()) + ->method('getCheckedMarker') + ->willReturn('[✔] '); + + $menuStyle + ->expects($this->any()) + ->method('getUncheckedMarker') + ->willReturn('[ ] '); + + $checkableItem1 = new CheckableItem('Item One', function () { + }); + + $checkableItem2 = new CheckableItem('Item Two', function () { + }); + + $item = new SplitItem( + [ + $checkableItem1, + $checkableItem2, + ] + ); + + $item->setSelectedItemIndex(0); + + self::assertEquals(['[ ] Item One [ ] Item Two '], $item->getRows($menuStyle, true)); + + $checkableItem1->toggle(); + + self::assertEquals(['[✔] Item One [ ] Item Two '], $item->getRows($menuStyle, true)); + } + + public function testRadioItem() : void + { + $menuStyle = $this->createMock(MenuStyle::class); + + $menuStyle + ->expects($this->any()) + ->method('getContentWidth') + ->will($this->returnValue(30)); + + $menuStyle + ->expects($this->any()) + ->method('getRadioMarker') + ->willReturn('[●] '); + + $menuStyle + ->expects($this->any()) + ->method('getUnradioMarker') + ->willReturn('[○] '); + + $checkableItem1 = new RadioItem('Item One', function () { + }); + + $checkableItem2 = new RadioItem('Item Two', function () { + }); + + $item = new SplitItem( + [ + $checkableItem1, + $checkableItem2, + ] + ); + + $cliMenu = $this->getMockBuilder(CliMenu::class) + ->disableOriginalConstructor() + ->onlyMethods(['getItems', 'redraw', 'getSelectedItemIndex', 'getItemByIndex']) + ->getMock(); + + $cliMenu->expects($this->never()) + ->method('getItems'); + + $cliMenu->expects($this->atLeastOnce()) + ->method('getSelectedItemIndex') + ->willReturn(1); + + $cliMenu->expects($this->atLeastOnce()) + ->method('getItemByIndex') + ->willReturn($item); + + $item->setSelectedItemIndex(0); + + self::assertEquals(['[○] Item One [○] Item Two '], $item->getRows($menuStyle, true)); + + $checkableItem1->getSelectAction()($cliMenu); + + self::assertEquals(['[●] Item One [○] Item Two '], $item->getRows($menuStyle, true)); + + $checkableItem2->getSelectAction()($cliMenu); + + self::assertEquals(['[○] Item One [●] Item Two '], $item->getRows($menuStyle, true)); + } } diff --git a/test/MenuStyleTest.php b/test/MenuStyleTest.php index 6f40fb35..6294f351 100644 --- a/test/MenuStyleTest.php +++ b/test/MenuStyleTest.php @@ -102,6 +102,8 @@ public function testGetterAndSetters() : void $style->setSelectedMarker('>'); $style->setUncheckedMarker('/'); $style->setCheckedMarker('+'); + $style->setUnradioMarker('O'); + $style->setRadioMarker('X'); $style->setItemExtra('EXTRA!'); $style->setDisplaysExtra(true); $style->setTitleSeparator('+'); @@ -121,6 +123,8 @@ public function testGetterAndSetters() : void self::assertSame('>', $style->getSelectedMarker()); self::assertEquals('/', $style->getUncheckedMarker()); self::assertEquals('+', $style->getCheckedMarker()); + self::assertEquals('O', $style->getUnradioMarker()); + self::assertEquals('X', $style->getRadioMarker()); self::assertSame('EXTRA!', $style->getItemExtra()); self::assertTrue($style->getDisplaysExtra()); self::assertSame('+', $style->getTitleSeparator());