From 0c42d7fe006d07cf915e589138d76bef73b39248 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 12:38:18 +0100 Subject: [PATCH 01/13] Item style refactoring: Introduce DefaultStyle item for non selectable items --- src/Builder/CliMenuBuilder.php | 19 ++---- src/CliMenu.php | 19 ++++++ src/MenuItem/AsciiArtItem.php | 22 +++++++ src/MenuItem/CheckboxItem.php | 12 ++-- src/MenuItem/LineBreakItem.php | 24 ++++++- src/MenuItem/MenuItemInterface.php | 8 +++ src/MenuItem/MenuMenuItem.php | 12 ++-- src/MenuItem/RadioItem.php | 12 ++-- src/MenuItem/SelectableItem.php | 12 ++-- src/MenuItem/SplitItem.php | 35 +++++++--- src/MenuItem/StaticItem.php | 16 +++-- src/Style/CheckboxStyle.php | 8 ++- src/Style/DefaultStyle.php | 26 ++++++++ src/Style/ItemStyle.php | 12 ++++ src/Style/RadioStyle.php | 8 ++- src/Style/SelectableStyle.php | 6 +- test/MenuItem/SplitItemTest.php | 102 +++++++++++++++-------------- test/Style/CheckboxStyleTest.php | 11 +++- test/Style/RadioStyleTest.php | 11 +++- test/Style/SelectableStyleTest.php | 6 +- 20 files changed, 270 insertions(+), 111 deletions(-) create mode 100644 src/Style/DefaultStyle.php create mode 100644 src/Style/ItemStyle.php diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index 5454cdf8..729afe40 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -585,24 +585,19 @@ private function propagateStyles(CliMenu $menu, array $items = []) : void $currentItems = !empty($items) ? $items : $menu->getItems(); foreach ($currentItems as $item) { - if ($item instanceof CheckboxItem - && !$item->getStyle()->hasChangedFromDefaults() - ) { + if ($item instanceof CheckboxItem && !$item->getStyle()->hasChangedFromDefaults()) { $item->setStyle(clone $menu->getCheckboxStyle()); } - if ($item instanceof RadioItem - && !$item->getStyle()->hasChangedFromDefaults() - ) { + if ($item instanceof RadioItem && !$item->getStyle()->hasChangedFromDefaults()) { $item->setStyle(clone $menu->getRadioStyle()); } - if (($item instanceof MenuMenuItem - || $item instanceof SelectableItem - || $item instanceof StaticItem - ) - && !$item->getStyle()->hasChangedFromDefaults() - ) { + if ($item instanceof StaticItem && !$item->getStyle()->hasChangedFromDefaults()) { + $item->setStyle(clone $menu->getDefaultStyle()); + } + + if (($item instanceof MenuMenuItem || $item instanceof SelectableItem) && !$item->getStyle()->hasChangedFromDefaults()) { $item->setStyle(clone $menu->getSelectableStyle()); } diff --git a/src/CliMenu.php b/src/CliMenu.php index c57ca5ff..f378727a 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -15,6 +15,7 @@ use PhpSchool\CliMenu\Dialogue\Confirm; use PhpSchool\CliMenu\Dialogue\Flash; use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\DefaultStyle; use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; @@ -53,6 +54,11 @@ class CliMenu */ private $selectableStyle; + /** + * @var DefaultStyle + */ + private $defaultStyle; + /** * @var ?string */ @@ -115,6 +121,7 @@ public function __construct( $this->checkboxStyle = new CheckboxStyle(); $this->radioStyle = new RadioStyle(); $this->selectableStyle = new SelectableStyle(); + $this->defaultStyle = new DefaultStyle(); $this->selectFirstItem(); } @@ -697,6 +704,18 @@ public function setSelectableStyle(SelectableStyle $style) : self return $this; } + public function getDefaultStyle() : DefaultStyle + { + return $this->defaultStyle; + } + + public function setDefaultStyle(DefaultStyle $style) : DefaultStyle + { + $this->defaultStyle = $style; + + return $this; + } + public function getCurrentFrame() : Frame { return $this->currentFrame; diff --git a/src/MenuItem/AsciiArtItem.php b/src/MenuItem/AsciiArtItem.php index 7fa1b3b3..a3c70d97 100644 --- a/src/MenuItem/AsciiArtItem.php +++ b/src/MenuItem/AsciiArtItem.php @@ -4,6 +4,8 @@ use Assert\Assertion; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\DefaultStyle; +use PhpSchool\CliMenu\Style\ItemStyle; /** * @author Michael Woodward @@ -37,6 +39,11 @@ class AsciiArtItem implements MenuItemInterface */ private $artLength; + /** + * @var DefaultStyle + */ + private $style; + public function __construct(string $text, string $position = self::POSITION_CENTER, string $alt = '') { Assertion::inArray($position, [self::POSITION_CENTER, self::POSITION_RIGHT, self::POSITION_LEFT]); @@ -44,6 +51,8 @@ public function __construct(string $text, string $position = self::POSITION_CENT $this->setText($text); $this->position = $position; $this->alternateText = $alt; + + $this->style = new DefaultStyle(); } /** @@ -161,4 +170,17 @@ public function hideItemExtra() : void { //noop } + + /** + * @return DefaultStyle + */ + public function getStyle() : ItemStyle + { + return $this->style; + } + + public function setStyle(DefaultStyle $style) : void + { + $this->style = $style; + } } diff --git a/src/MenuItem/CheckboxItem.php b/src/MenuItem/CheckboxItem.php index 7777355f..55f8de62 100644 --- a/src/MenuItem/CheckboxItem.php +++ b/src/MenuItem/CheckboxItem.php @@ -6,6 +6,7 @@ use PhpSchool\CliMenu\MenuStyle; use PhpSchool\CliMenu\Util\StringUtil; use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\ItemStyle; class CheckboxItem implements MenuItemInterface { @@ -60,7 +61,7 @@ public function __construct( */ public function getRows(MenuStyle $style, bool $selected = false) : array { - $marker = sprintf("%s", $this->style->getMarker($this->checked)); + $marker = sprintf("%s", $this->style->getMarker($this, $selected)); $itemExtra = $this->style->getItemExtra(); @@ -183,15 +184,16 @@ public function toggle() : void $this->checked = !$this->checked; } - public function getStyle() : CheckboxStyle + /** + * @return CheckboxStyle + */ + public function getStyle() : ItemStyle { return $this->style; } - public function setStyle(CheckboxStyle $style) : self + public function setStyle(CheckboxStyle $style) : void { $this->style = $style; - - return $this; } } diff --git a/src/MenuItem/LineBreakItem.php b/src/MenuItem/LineBreakItem.php index 0b22a52c..ac07dedc 100644 --- a/src/MenuItem/LineBreakItem.php +++ b/src/MenuItem/LineBreakItem.php @@ -4,6 +4,8 @@ use Assert\Assertion; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\DefaultStyle; +use PhpSchool\CliMenu\Style\ItemStyle; /** * @author Michael Woodward @@ -20,10 +22,17 @@ class LineBreakItem implements MenuItemInterface */ private $lines; + /** + * @var DefaultStyle + */ + private $style; + public function __construct(string $breakChar = ' ', int $lines = 1) { $this->breakChar = $breakChar; - $this->lines = $lines; + $this->lines = $lines; + + $this->style = new DefaultStyle(); } /** @@ -100,4 +109,17 @@ public function hideItemExtra() : void { //noop } + + /** + * @return DefaultStyle + */ + public function getStyle() : ItemStyle + { + return $this->style; + } + + public function setStyle(DefaultStyle $style) : void + { + $this->style = $style; + } } diff --git a/src/MenuItem/MenuItemInterface.php b/src/MenuItem/MenuItemInterface.php index 0561fce0..971c5d53 100644 --- a/src/MenuItem/MenuItemInterface.php +++ b/src/MenuItem/MenuItemInterface.php @@ -3,6 +3,7 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\ItemStyle; /** * @author Michael Woodward @@ -43,4 +44,11 @@ public function showItemExtra() : void; * Disable showing item extra */ public function hideItemExtra() : void; + + /** + * Get the items style object. Can and + * should be subclassed to provide bespoke + * behaviour. + */ + public function getStyle() : ItemStyle; } diff --git a/src/MenuItem/MenuMenuItem.php b/src/MenuItem/MenuMenuItem.php index faca885c..cd3dfe6d 100644 --- a/src/MenuItem/MenuMenuItem.php +++ b/src/MenuItem/MenuMenuItem.php @@ -5,6 +5,7 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuStyle; use PhpSchool\CliMenu\Util\StringUtil; +use PhpSchool\CliMenu\Style\ItemStyle; use PhpSchool\CliMenu\Style\SelectableStyle; use function PhpSchool\CliMenu\Util\mapWithKeys; @@ -55,7 +56,7 @@ public function __construct( */ public function getRows(MenuStyle $style, bool $selected = false) : array { - $marker = sprintf("%s", $this->style->getMarker($selected)); + $marker = sprintf("%s", $this->style->getMarker($this, $selected)); $length = $this->style->getDisplaysExtra() ? $style->getContentWidth() - (mb_strlen($this->style->getItemExtra()) + 2) @@ -163,15 +164,16 @@ public function hideItemExtra() : void $this->showItemExtra = false; } - public function getStyle() : SelectableStyle + /** + * @return SelectableStyle + */ + public function getStyle() : ItemStyle { return $this->style; } - public function setStyle(SelectableStyle $style) : self + public function setStyle(SelectableStyle $style) : void { $this->style = $style; - - return $this; } } diff --git a/src/MenuItem/RadioItem.php b/src/MenuItem/RadioItem.php index 8bb20566..4001cf3e 100644 --- a/src/MenuItem/RadioItem.php +++ b/src/MenuItem/RadioItem.php @@ -5,6 +5,7 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuStyle; use PhpSchool\CliMenu\Util\StringUtil; +use PhpSchool\CliMenu\Style\ItemStyle; use PhpSchool\CliMenu\Style\RadioStyle; class RadioItem implements MenuItemInterface @@ -60,7 +61,7 @@ public function __construct( */ public function getRows(MenuStyle $style, bool $selected = false) : array { - $marker = sprintf("%s", $this->style->getMarker($this->checked)); + $marker = sprintf("%s", $this->style->getMarker($this, $selected)); $itemExtra = $this->style->getItemExtra(); @@ -203,15 +204,16 @@ public function toggle() : void $this->checked = !$this->checked; } - public function getStyle() : RadioStyle + /** + * @return RadioStyle + */ + public function getStyle() : ItemStyle { return $this->style; } - public function setStyle(RadioStyle $style) : self + public function setStyle(RadioStyle $style) : void { $this->style = $style; - - return $this; } } diff --git a/src/MenuItem/SelectableItem.php b/src/MenuItem/SelectableItem.php index 509a1c1b..7e952ea0 100644 --- a/src/MenuItem/SelectableItem.php +++ b/src/MenuItem/SelectableItem.php @@ -3,6 +3,7 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\ItemStyle; use PhpSchool\CliMenu\Util\StringUtil; use PhpSchool\CliMenu\Style\SelectableStyle; use function PhpSchool\CliMenu\Util\mapWithKeys; @@ -56,7 +57,7 @@ public function __construct( */ public function getRows(MenuStyle $style, bool $selected = false) : array { - $marker = sprintf("%s", $this->style->getMarker($selected)); + $marker = sprintf("%s", $this->style->getMarker($this, $selected)); $length = $this->style->getDisplaysExtra() ? $style->getContentWidth() - (mb_strlen($this->style->getItemExtra()) + 2) @@ -145,15 +146,16 @@ public function hideItemExtra() : void $this->showItemExtra = false; } - public function getStyle() : SelectableStyle + /** + * @return SelectableStyle + */ + public function getStyle() : ItemStyle { return $this->style; } - public function setStyle(SelectableStyle $style) : self + public function setStyle(SelectableStyle $style) : void { $this->style = $style; - - return $this; } } diff --git a/src/MenuItem/SplitItem.php b/src/MenuItem/SplitItem.php index 7eec8b00..05cc06bf 100644 --- a/src/MenuItem/SplitItem.php +++ b/src/MenuItem/SplitItem.php @@ -4,6 +4,9 @@ use Assert\Assertion; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\DefaultStyle; +use PhpSchool\CliMenu\Style\ItemStyle; +use PhpSchool\CliMenu\Style\Selectable; use PhpSchool\CliMenu\Util\StringUtil; use function PhpSchool\CliMenu\Util\mapWithKeys; @@ -32,6 +35,11 @@ class SplitItem implements MenuItemInterface */ private $gutter = 2; + /** + * @var DefaultStyle + */ + private $style; + /** * @var array */ @@ -45,6 +53,8 @@ public function __construct(array $items = []) { $this->addItems($items); $this->setDefaultSelectedItem(); + + $this->style = new DefaultStyle(); } public function getGutter() : int @@ -133,17 +143,11 @@ public function getRows(MenuStyle $style, bool $selected = false) : array mapWithKeys($this->items, function (int $index, MenuItemInterface $item) use ($selected, $length, $style) { $isSelected = $selected && $index === $this->selectedItemIndex; - if ($item instanceof CheckboxItem || $item instanceof RadioItem) { - $markerType = $item->getStyle()->getMarker($item->getChecked()); - } else { - /** @var MenuMenuItem|SelectableItem|StaticItem $item */ - $markerType = $item->getStyle()->getMarker($isSelected); + $marker = ''; + if ($item->canSelect()) { + $marker = $item->getStyle()->getMarker($item, $isSelected); } - $marker = $item->canSelect() - ? sprintf('%s', $markerType) - : ''; - $itemExtra = ''; if ($item->getStyle()->getDisplaysExtra()) { $itemExtraVal = $item->getStyle()->getItemExtra(); @@ -349,4 +353,17 @@ private function calculateItemExtra() : int return $largestItemExtra; } + + /** + * @return DefaultStyle + */ + public function getStyle(): ItemStyle + { + return $this->style; + } + + public function setStyle(DefaultStyle $style): void + { + $this->style = $style; + } } diff --git a/src/MenuItem/StaticItem.php b/src/MenuItem/StaticItem.php index efa326de..87c53f69 100644 --- a/src/MenuItem/StaticItem.php +++ b/src/MenuItem/StaticItem.php @@ -3,8 +3,9 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\DefaultStyle; +use PhpSchool\CliMenu\Style\ItemStyle; use PhpSchool\CliMenu\Util\StringUtil; -use PhpSchool\CliMenu\Style\SelectableStyle; /** * @author Michael Woodward @@ -17,7 +18,7 @@ class StaticItem implements MenuItemInterface private $text; /** - * @var SelectableStyle; + * @var DefaultStyle */ private $style; @@ -25,7 +26,7 @@ public function __construct(string $text) { $this->text = $text; - $this->style = new SelectableStyle(); + $this->style = new DefaultStyle(); } /** @@ -92,15 +93,16 @@ public function hideItemExtra() : void //noop } - public function getStyle() : SelectableStyle + /** + * @return DefaultStyle + */ + public function getStyle() : ItemStyle { return $this->style; } - public function setStyle(SelectableStyle $style) : self + public function setStyle(DefaultStyle $style) : void { $this->style = $style; - - return $this; } } diff --git a/src/Style/CheckboxStyle.php b/src/Style/CheckboxStyle.php index 36339eb5..c746c1d2 100644 --- a/src/Style/CheckboxStyle.php +++ b/src/Style/CheckboxStyle.php @@ -2,7 +2,9 @@ namespace PhpSchool\CliMenu\Style; -class CheckboxStyle +use PhpSchool\CliMenu\MenuItem\CheckboxItem; + +class CheckboxStyle implements ItemStyle { private const DEFAULT_STYLES = [ 'checkedMarker' => '[✔] ', @@ -51,9 +53,9 @@ public function hasChangedFromDefaults() : bool return $currentValues !== array_values(self::DEFAULT_STYLES); } - public function getMarker(bool $selected) : string + public function getMarker(CheckboxItem $item, bool $selected) : string { - return $selected ? $this->checkedMarker : $this->uncheckedMarker; + return $item->getChecked() ? $this->checkedMarker : $this->uncheckedMarker; } public function getCheckedMarker() : string diff --git a/src/Style/DefaultStyle.php b/src/Style/DefaultStyle.php new file mode 100644 index 00000000..f54f04a1 --- /dev/null +++ b/src/Style/DefaultStyle.php @@ -0,0 +1,26 @@ + '[●] ', @@ -51,9 +53,9 @@ public function hasChangedFromDefaults() : bool return $currentValues !== array_values(self::DEFAULT_STYLES); } - public function getMarker(bool $selected) : string + public function getMarker(RadioItem $item, bool $selected) : string { - return $selected ? $this->checkedMarker : $this->uncheckedMarker; + return $item->getChecked() ? $this->checkedMarker : $this->uncheckedMarker; } public function getCheckedMarker() : string diff --git a/src/Style/SelectableStyle.php b/src/Style/SelectableStyle.php index ad78d4d1..d0224186 100644 --- a/src/Style/SelectableStyle.php +++ b/src/Style/SelectableStyle.php @@ -2,7 +2,9 @@ namespace PhpSchool\CliMenu\Style; -class SelectableStyle +use PhpSchool\CliMenu\MenuItem\MenuItemInterface; + +class SelectableStyle implements ItemStyle { private const DEFAULT_STYLES = [ 'selectedMarker' => '● ', @@ -51,7 +53,7 @@ public function hasChangedFromDefaults() : bool return $currentValues !== array_values(self::DEFAULT_STYLES); } - public function getMarker(bool $selected) : string + public function getMarker(MenuItemInterface $item, bool $selected) : string { return $selected ? $this->selectedMarker : $this->unselectedMarker; } diff --git a/test/MenuItem/SplitItemTest.php b/test/MenuItem/SplitItemTest.php index b1748e01..2d21f448 100644 --- a/test/MenuItem/SplitItemTest.php +++ b/test/MenuItem/SplitItemTest.php @@ -198,14 +198,15 @@ public function testGetRowsWithOneItemSelected() : void ->setSelectedMarker('= ') ->setUnselectedMarker('* '); - $item = new SplitItem( - [ - (new SelectableItem('Item One', function () { - }))->setStyle($selectableStyle), - (new SelectableItem('Item Two', function () { - }))->setStyle($selectableStyle), - ] - ); + $cb = function () { + }; + + $item1 = new SelectableItem("Item One", $cb); + $item1->setStyle($selectableStyle); + $item2 = new SelectableItem("Item Two", $cb); + $item2->setStyle($selectableStyle); + + $item = new SplitItem([$item1, $item2]); $item->setSelectedItemIndex(0); @@ -244,14 +245,15 @@ public function testGetRowsWithMultipleLinesWithUnSelectedMarker() : void $selectableStyle = (new SelectableStyle()) ->setUnselectedMarker('* '); - $item = new SplitItem( - [ - (new SelectableItem("Item\nOne", function () { - }))->setStyle($selectableStyle), - (new SelectableItem("Item\nTwo", function () { - }))->setStyle($selectableStyle), - ] - ); + $cb = function () { + }; + + $item1 = new SelectableItem("Item\nOne", $cb); + $item1->setStyle($selectableStyle); + $item2 = new SelectableItem("Item\nTwo", $cb); + $item2->setStyle($selectableStyle); + + $item = new SplitItem([$item1, $item2]); self::assertEquals( [ @@ -275,14 +277,15 @@ public function testGetRowsWithMultipleLinesWithOneItemSelected() : void ->setSelectedMarker('= ') ->setUnselectedMarker('* '); - $item = new SplitItem( - [ - (new SelectableItem("Item\nOne", function () { - }))->setStyle($selectableStyle), - (new SelectableItem("Item\nTwo", function () { - }))->setStyle($selectableStyle), - ] - ); + $cb = function () { + }; + + $item1 = new SelectableItem("Item\nOne", $cb); + $item1->setStyle($selectableStyle); + $item2 = new SelectableItem("Item\nTwo", $cb); + $item2->setStyle($selectableStyle); + + $item = new SplitItem([$item1, $item2]); $item->setSelectedItemIndex(0); @@ -309,14 +312,15 @@ public function testGetRowsWithItemExtra() : void ->setDisplaysExtra(true) ->setUnselectedMarker('* '); - $item = new SplitItem( - [ - (new SelectableItem('Item 1', function () { - }, true))->setStyle($selectableStyle), - (new SelectableItem('Item 2', function () { - }, true))->setStyle($selectableStyle), - ] - ); + $cb = function () { + }; + + $item1 = new SelectableItem('Item 1', $cb, true); + $item1->setStyle($selectableStyle); + $item2 = new SelectableItem('Item 2', $cb, true); + $item2->setStyle($selectableStyle); + + $item = new SplitItem([$item1, $item2]); self::assertEquals(['* Item 1 [EXTRA] * Item 2 [EXTRA] '], $item->getRows($menuStyle)); } @@ -335,14 +339,15 @@ public function testGetRowsWithMultipleLinesWithItemExtra() : void ->setDisplaysExtra(true) ->setUnselectedMarker('* '); - $item = new SplitItem( - [ - (new SelectableItem("Item 1\nItem 1", function () { - }, true))->setStyle($selectableStyle), - (new SelectableItem("Item 2\nItem 2", function () { - }, true))->setStyle($selectableStyle), - ] - ); + $cb = function () { + }; + + $item1 = new SelectableItem("Item 1\nItem 1", $cb, true); + $item1->setStyle($selectableStyle); + $item2 = new SelectableItem("Item 2\nItem 2", $cb, true); + $item2->setStyle($selectableStyle); + + $item = new SplitItem([$item1, $item2]); self::assertEquals( [ @@ -367,14 +372,15 @@ public function testGetRowsWithMultipleLinesWithItemExtraOnOne() : void ->setDisplaysExtra(true) ->setUnselectedMarker('* '); - $item = new SplitItem( - [ - (new SelectableItem("Item 1\nItem 1", function () { - }))->setStyle($selectableStyle), - (new SelectableItem("Item 2\nItem 2", function () { - }, true))->setStyle($selectableStyle), - ] - ); + $cb = function () { + }; + + $item1 = new SelectableItem("Item 1\nItem 1", $cb); + $item1->setStyle($selectableStyle); + $item2 = new SelectableItem("Item 2\nItem 2", $cb, true); + $item2->setStyle($selectableStyle); + + $item = new SplitItem([$item1, $item2]); self::assertEquals( [ diff --git a/test/Style/CheckboxStyleTest.php b/test/Style/CheckboxStyleTest.php index 4321790e..ed8e2fbb 100644 --- a/test/Style/CheckboxStyleTest.php +++ b/test/Style/CheckboxStyleTest.php @@ -4,6 +4,7 @@ namespace PhpSchool\CliMenuTest\Style; +use PhpSchool\CliMenu\MenuItem\CheckboxItem; use PhpSchool\CliMenu\Style\CheckboxStyle; use PHPUnit\Framework\TestCase; @@ -16,10 +17,16 @@ public function testHasChangedFromDefaultsWhenNoStylesChanged() : void public function testGetMarker() : void { + $item = new CheckboxItem('My Checkbox', 'var_dump'); + $item->setChecked(); + $style = new CheckboxStyle; - self::assertSame('[✔] ', $style->getMarker(true)); - self::assertSame('[ ] ', $style->getMarker(false)); + self::assertSame('[✔] ', $style->getMarker($item, false)); + + $item->setUnchecked(); + + self::assertSame('[ ] ', $style->getMarker($item, false)); } public function testGetSetMarkerOn() : void diff --git a/test/Style/RadioStyleTest.php b/test/Style/RadioStyleTest.php index 9edf2804..4630c63b 100644 --- a/test/Style/RadioStyleTest.php +++ b/test/Style/RadioStyleTest.php @@ -4,6 +4,7 @@ namespace PhpSchool\CliMenuTest\Style; +use PhpSchool\CliMenu\MenuItem\RadioItem; use PhpSchool\CliMenu\Style\RadioStyle; use PHPUnit\Framework\TestCase; @@ -16,10 +17,16 @@ public function testHasChangedFromDefaultsWhenNoStylesChanged() : void public function testGetMarker() : void { + $item = new RadioItem('My Radio', 'var_dump'); + $item->setChecked(); + $style = new RadioStyle; - self::assertSame('[●] ', $style->getMarker(true)); - self::assertSame('[○] ', $style->getMarker(false)); + self::assertSame('[●] ', $style->getMarker($item, false)); + + $item->setUnchecked(); + + self::assertSame('[○] ', $style->getMarker($item, false)); } public function testGetSetMarkerOn() : void diff --git a/test/Style/SelectableStyleTest.php b/test/Style/SelectableStyleTest.php index 27c4c439..d8311b6f 100644 --- a/test/Style/SelectableStyleTest.php +++ b/test/Style/SelectableStyleTest.php @@ -4,6 +4,7 @@ namespace PhpSchool\CliMenuTest\Style; +use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\Style\SelectableStyle; use PHPUnit\Framework\TestCase; @@ -16,10 +17,11 @@ public function testHasChangedFromDefaultsWhenNoStylesChanged() : void public function testGetMarker() : void { + $item = new SelectableItem('My Item', 'var_dump'); $style = new SelectableStyle; - self::assertSame('● ', $style->getMarker(true)); - self::assertSame('○ ', $style->getMarker(false)); + self::assertSame('● ', $style->getMarker($item, true)); + self::assertSame('○ ', $style->getMarker($item, false)); } public function testGetSetMarkerOn() : void From a92af214f57255c401fd011ad29d4b6dc6db6e6b Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 12:42:59 +0100 Subject: [PATCH 02/13] CS --- src/Builder/CliMenuBuilder.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index 729afe40..f724cf58 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -597,7 +597,8 @@ private function propagateStyles(CliMenu $menu, array $items = []) : void $item->setStyle(clone $menu->getDefaultStyle()); } - if (($item instanceof MenuMenuItem || $item instanceof SelectableItem) && !$item->getStyle()->hasChangedFromDefaults()) { + if (($item instanceof MenuMenuItem || $item instanceof SelectableItem) + && !$item->getStyle()->hasChangedFromDefaults()) { $item->setStyle(clone $menu->getSelectableStyle()); } From 5332400d392643070e07fbf96772a32b311e5bd2 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 12:43:49 +0100 Subject: [PATCH 03/13] Fix typehint --- src/CliMenu.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CliMenu.php b/src/CliMenu.php index f378727a..897e1e38 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -709,7 +709,7 @@ public function getDefaultStyle() : DefaultStyle return $this->defaultStyle; } - public function setDefaultStyle(DefaultStyle $style) : DefaultStyle + public function setDefaultStyle(DefaultStyle $style) : self { $this->defaultStyle = $style; From 8aacbe5b9e48bf9c7b1311340a7b85e547dfba8f Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 12:56:23 +0100 Subject: [PATCH 04/13] Add getMarker() to ItemStyleInterface --- src/Style/CheckboxStyle.php | 9 ++++++++- src/Style/DefaultStyle.php | 7 +++++++ src/Style/ItemStyle.php | 4 ++++ src/Style/RadioStyle.php | 9 ++++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Style/CheckboxStyle.php b/src/Style/CheckboxStyle.php index c746c1d2..ed9a6ae4 100644 --- a/src/Style/CheckboxStyle.php +++ b/src/Style/CheckboxStyle.php @@ -3,6 +3,7 @@ namespace PhpSchool\CliMenu\Style; use PhpSchool\CliMenu\MenuItem\CheckboxItem; +use PhpSchool\CliMenu\MenuItem\MenuItemInterface; class CheckboxStyle implements ItemStyle { @@ -53,8 +54,14 @@ public function hasChangedFromDefaults() : bool return $currentValues !== array_values(self::DEFAULT_STYLES); } - public function getMarker(CheckboxItem $item, bool $selected) : string + public function getMarker(MenuItemInterface $item, bool $selected) : string { + if (!$item instanceof CheckboxItem) { + throw new \InvalidArgumentException( + sprintf('Expected an instance of: %s. Got: %s', CheckboxItem::class, get_class($item)) + ); + } + return $item->getChecked() ? $this->checkedMarker : $this->uncheckedMarker; } diff --git a/src/Style/DefaultStyle.php b/src/Style/DefaultStyle.php index f54f04a1..55517550 100644 --- a/src/Style/DefaultStyle.php +++ b/src/Style/DefaultStyle.php @@ -4,6 +4,8 @@ namespace PhpSchool\CliMenu\Style; +use PhpSchool\CliMenu\MenuItem\MenuItemInterface; + class DefaultStyle implements ItemStyle { private const DEFAULT_STYLES = [ @@ -23,4 +25,9 @@ public function getItemExtra() : string { return ''; } + + public function getMarker(MenuItemInterface $style, bool $isSelected) : string + { + return ''; + } } diff --git a/src/Style/ItemStyle.php b/src/Style/ItemStyle.php index e86a6807..261c1c3d 100644 --- a/src/Style/ItemStyle.php +++ b/src/Style/ItemStyle.php @@ -4,9 +4,13 @@ namespace PhpSchool\CliMenu\Style; +use PhpSchool\CliMenu\MenuItem\MenuItemInterface; + interface ItemStyle { public function getDisplaysExtra() : bool; public function getItemExtra() : string; + + public function getMarker(MenuItemInterface $menuItem, bool $isSelected) : string; } diff --git a/src/Style/RadioStyle.php b/src/Style/RadioStyle.php index 0be65724..63cb592a 100644 --- a/src/Style/RadioStyle.php +++ b/src/Style/RadioStyle.php @@ -2,6 +2,7 @@ namespace PhpSchool\CliMenu\Style; +use PhpSchool\CliMenu\MenuItem\MenuItemInterface; use PhpSchool\CliMenu\MenuItem\RadioItem; class RadioStyle implements ItemStyle @@ -53,8 +54,14 @@ public function hasChangedFromDefaults() : bool return $currentValues !== array_values(self::DEFAULT_STYLES); } - public function getMarker(RadioItem $item, bool $selected) : string + public function getMarker(MenuItemInterface $item, bool $selected) : string { + if (!$item instanceof RadioItem) { + throw new \InvalidArgumentException( + sprintf('Expected an instance of: %s. Got: %s', RadioItem::class, get_class($item)) + ); + } + return $item->getChecked() ? $this->checkedMarker : $this->uncheckedMarker; } From 7e2a140fca0edc41ae6a10e2eaa32802340ba4fe Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 13:17:14 +0100 Subject: [PATCH 05/13] Refactor SplitItem#calculateItemExtra --- src/MenuItem/SplitItem.php | 25 +++++++++---------------- src/Util/ArrayUtils.php | 5 +++++ test/Util/ArrayUtilTest.php | 8 ++++++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/MenuItem/SplitItem.php b/src/MenuItem/SplitItem.php index 05cc06bf..3e824c90 100644 --- a/src/MenuItem/SplitItem.php +++ b/src/MenuItem/SplitItem.php @@ -9,6 +9,7 @@ use PhpSchool\CliMenu\Style\Selectable; use PhpSchool\CliMenu\Util\StringUtil; use function PhpSchool\CliMenu\Util\mapWithKeys; +use function PhpSchool\CliMenu\Util\max; /** * @author Michael Woodward @@ -336,22 +337,14 @@ public function getText() : string */ private function calculateItemExtra() : int { - $largestItemExtra = 0; - - /** @var CheckboxItem|RadioItem|MenuMenuItem|SelectableItem|StaticItem $item */ - foreach ($this->items as $item) { - if (!$item->getStyle()->getDisplaysExtra()) { - continue; - } - - if (mb_strlen($item->getStyle()->getItemExtra()) < $largestItemExtra) { - continue; - } - - $largestItemExtra = mb_strlen($item->getStyle()->getItemExtra()); - } - - return $largestItemExtra; + return max(array_map( + function (MenuItemInterface $item) { + return mb_strlen($item->getStyle()->getItemExtra()); + }, + array_filter($this->items, function (MenuItemInterface $item) { + return $item->getStyle()->getDisplaysExtra(); + }) + )); } /** diff --git a/src/Util/ArrayUtils.php b/src/Util/ArrayUtils.php index 60aad3e8..d407f73d 100644 --- a/src/Util/ArrayUtils.php +++ b/src/Util/ArrayUtils.php @@ -18,3 +18,8 @@ function each(array $array, callable $callback) : void $callback($k, $v); } } + +function max(array $items) : int +{ + return count($items) > 0 ? \max($items) : 0; +} diff --git a/test/Util/ArrayUtilTest.php b/test/Util/ArrayUtilTest.php index 895f2bdf..34e349d1 100644 --- a/test/Util/ArrayUtilTest.php +++ b/test/Util/ArrayUtilTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use function PhpSchool\CliMenu\Util\each; use function PhpSchool\CliMenu\Util\mapWithKeys; +use function PhpSchool\CliMenu\Util\max; class ArrayUtilTest extends TestCase { @@ -47,4 +48,11 @@ public function testEach() : void each([1, 2, 3], $cb); self::assertEquals(3, $i); } + + public function testMax() : void + { + self::assertEquals(0, max([])); + self::assertEquals(3, max([1, 2, 3])); + self::assertEquals(6, max([1, 6, 3])); + } } From 822c0920594fa3891f54220cab57ccf6342a67ec Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 17:35:07 +0100 Subject: [PATCH 06/13] Refactor style propagation --- src/Builder/CliMenuBuilder.php | 94 +++++++++++------------ src/CliMenu.php | 73 +++++------------- src/Style/CheckboxStyle.php | 2 +- src/Style/Customisable.php | 10 +++ src/Style/DefaultStyle.php | 4 +- src/Style/Exception/InvalidStyle.php | 13 ++++ src/Style/Locator.php | 107 +++++++++++++++++++++++++++ src/Style/RadioStyle.php | 2 +- src/Style/SelectableStyle.php | 2 +- test/Style/LocatorTest.php | 85 +++++++++++++++++++++ 10 files changed, 280 insertions(+), 112 deletions(-) create mode 100644 src/Style/Customisable.php create mode 100644 src/Style/Exception/InvalidStyle.php create mode 100644 src/Style/Locator.php create mode 100644 test/Style/LocatorTest.php diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index f724cf58..834126d5 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -17,6 +17,7 @@ use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\DefaultStyle; use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; @@ -397,7 +398,7 @@ public function setMargin(int $margin) : self public function setItemExtra(string $extra) : self { $this->style->setItemExtra($extra); - $this->menu->getSelectableStyle()->setItemExtra($extra); + $this->getSelectableStyle()->setItemExtra($extra); // if we customise item extra, it means we most likely want to display it $this->displayExtra(); @@ -490,7 +491,7 @@ public function disableDefaultItems() : self public function displayExtra() : self { $this->style->setDisplaysExtra(true); - $this->menu->getSelectableStyle()->setDisplaysExtra(true); + $this->getSelectableStyle()->setDisplaysExtra(true); return $this; } @@ -519,59 +520,78 @@ public function build() : CliMenu return $this->menu; } - public function getCheckboxStyle() : CheckboxStyle + public function getDefaultStyle() : DefaultStyle { - return $this->menu->getCheckboxStyle(); + return $this->menu->getItemStyle(DefaultStyle::class); } - public function setCheckboxStyle(CheckboxStyle $style) : self + public function setDefaultStyle(DefaultStyle $style) : self { - $this->menu->setCheckboxStyle($style); + $this->menu->setItemStyle($style, DefaultStyle::class); return $this; } - public function modifyCheckboxStyle(callable $itemCallable) : self + public function modifyDefaultStyle(callable $itemCallable) : self { - $itemCallable($this->menu->getCheckboxStyle()); + $itemCallable($this->getDefaultStyle()); return $this; } - public function getRadioStyle() : RadioStyle + public function getSelectableStyle() : SelectableStyle { - return $this->menu->getRadioStyle(); + return $this->menu->getItemStyle(SelectableStyle::class); } - public function setRadioStyle(RadioStyle $style) : self + public function setSelectableStyle(SelectableStyle $style) : self { - $this->menu->setRadioStyle($style); + $this->menu->setItemStyle($style, SelectableStyle::class); return $this; } - public function modifyRadioStyle(callable $itemCallable) : self + public function modifySelectableStyle(callable $itemCallable) : self { - $itemCallable($this->menu->getRadioStyle()); + $itemCallable($this->getSelectableStyle()); return $this; } - public function getSelectableStyle() : SelectableStyle + public function getCheckboxStyle() : CheckboxStyle { - return $this->menu->getSelectableStyle(); + return $this->menu->getItemStyle(CheckboxStyle::class); } - public function setSelectableStyle(SelectableStyle $style) : self + public function setCheckboxStyle(CheckboxStyle $style) : self { - $this->menu->setSelectableStyle($style); + $this->menu->setItemStyle($style, CheckboxStyle::class); return $this; } - public function modifySelectableStyle(callable $itemCallable) : self + public function modifyCheckboxStyle(callable $itemCallable) : self { - $itemCallable($this->menu->getSelectableStyle()); + $itemCallable($this->getCheckboxStyle()); + + return $this; + } + + public function getRadioStyle() : RadioStyle + { + return $this->menu->getItemStyle(RadioItem::class); + } + + public function setRadioStyle(RadioStyle $style) : self + { + $this->menu->setItemStyle($style, RadioItem::class); + + return $this; + } + + public function modifyRadioStyle(callable $itemCallable) : self + { + $itemCallable($this->getRadioStyle()); return $this; } @@ -585,43 +605,15 @@ private function propagateStyles(CliMenu $menu, array $items = []) : void $currentItems = !empty($items) ? $items : $menu->getItems(); foreach ($currentItems as $item) { - if ($item instanceof CheckboxItem && !$item->getStyle()->hasChangedFromDefaults()) { - $item->setStyle(clone $menu->getCheckboxStyle()); - } - - if ($item instanceof RadioItem && !$item->getStyle()->hasChangedFromDefaults()) { - $item->setStyle(clone $menu->getRadioStyle()); - } - - if ($item instanceof StaticItem && !$item->getStyle()->hasChangedFromDefaults()) { - $item->setStyle(clone $menu->getDefaultStyle()); - } - - if (($item instanceof MenuMenuItem || $item instanceof SelectableItem) - && !$item->getStyle()->hasChangedFromDefaults()) { - $item->setStyle(clone $menu->getSelectableStyle()); + if (!$item->getStyle()->hasChangedFromDefaults()) { + $item->setStyle(clone $menu->getItemStyleForItem($item)); } // Apply current style to children, if they are not customized if ($item instanceof MenuMenuItem) { $subMenu = $item->getSubMenu(); - if (!$subMenu->getStyle()->hasChangedFromDefaults()) { - $subMenu->setStyle(clone $menu->getStyle()); - } - - if (!$subMenu->getCheckboxStyle()->hasChangedFromDefaults()) { - $subMenu->setCheckboxStyle(clone $menu->getCheckboxStyle()); - } - - if (!$subMenu->getRadioStyle()->hasChangedFromDefaults()) { - $subMenu->setRadioStyle(clone $menu->getRadioStyle()); - } - - if (!$subMenu->getSelectableStyle()->hasChangedFromDefaults()) { - $subMenu->setSelectableStyle(clone $menu->getSelectableStyle()); - } - + $subMenu->importStyles($menu); $this->propagateStyles($subMenu); } diff --git a/src/CliMenu.php b/src/CliMenu.php index 897e1e38..52059618 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -16,6 +16,8 @@ use PhpSchool\CliMenu\Dialogue\Flash; use PhpSchool\CliMenu\Style\CheckboxStyle; use PhpSchool\CliMenu\Style\DefaultStyle; +use PhpSchool\CliMenu\Style\ItemStyle; +use PhpSchool\CliMenu\Style\Locator; use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; @@ -40,24 +42,9 @@ class CliMenu protected $style; /** - * @var CheckboxStyle + * @var Locator */ - private $checkboxStyle; - - /** - * @var RadioStyle - */ - private $radioStyle; - - /** - * @var SelectableStyle - */ - private $selectableStyle; - - /** - * @var DefaultStyle - */ - private $defaultStyle; + private $itemStyleLocator; /** * @var ?string @@ -118,10 +105,8 @@ public function __construct( $this->items = $items; $this->terminal = $terminal ?: TerminalFactory::fromSystem(); $this->style = $style ?: new MenuStyle($this->terminal); - $this->checkboxStyle = new CheckboxStyle(); - $this->radioStyle = new RadioStyle(); - $this->selectableStyle = new SelectableStyle(); - $this->defaultStyle = new DefaultStyle(); + + $this->itemStyleLocator = new Locator(); $this->selectFirstItem(); } @@ -668,52 +653,28 @@ public function setStyle(MenuStyle $style) : void $this->style = $style; } - public function getCheckboxStyle() : CheckboxStyle - { - return $this->checkboxStyle; - } - - public function setCheckboxStyle(CheckboxStyle $style) : self + public function setItemStyle(ItemStyle $style, string $styleClass) { - $this->checkboxStyle = $style; - - return $this; + $this->itemStyleLocator->setStyle($style, $styleClass); } - public function getRadioStyle() : RadioStyle + public function getItemStyle(string $styleClass) : ItemStyle { - return $this->radioStyle; + return $this->itemStyleLocator->getStyle($styleClass); } - public function setRadioStyle(RadioStyle $style) : self + public function getItemStyleForItem(MenuItemInterface $item) : ItemStyle { - $this->radioStyle = $style; - - return $this; + return $this->itemStyleLocator->getStyleForMenuItem($item); } - public function getSelectableStyle() : SelectableStyle + public function importStyles(CliMenu $menu) : void { - return $this->selectableStyle; - } - - public function setSelectableStyle(SelectableStyle $style) : self - { - $this->selectableStyle = $style; - - return $this; - } - - public function getDefaultStyle() : DefaultStyle - { - return $this->defaultStyle; - } - - public function setDefaultStyle(DefaultStyle $style) : self - { - $this->defaultStyle = $style; + if (!$this->style->hasChangedFromDefaults()) { + $this->style = $menu->style; + } - return $this; + $this->itemStyleLocator->importFrom($menu->itemStyleLocator); } public function getCurrentFrame() : Frame diff --git a/src/Style/CheckboxStyle.php b/src/Style/CheckboxStyle.php index ed9a6ae4..0d3030cf 100644 --- a/src/Style/CheckboxStyle.php +++ b/src/Style/CheckboxStyle.php @@ -5,7 +5,7 @@ use PhpSchool\CliMenu\MenuItem\CheckboxItem; use PhpSchool\CliMenu\MenuItem\MenuItemInterface; -class CheckboxStyle implements ItemStyle +class CheckboxStyle implements ItemStyle, Customisable { private const DEFAULT_STYLES = [ 'checkedMarker' => '[✔] ', diff --git a/src/Style/Customisable.php b/src/Style/Customisable.php new file mode 100644 index 00000000..4775c68c --- /dev/null +++ b/src/Style/Customisable.php @@ -0,0 +1,10 @@ + DefaultStyle::class, + AsciiArtItem::class => DefaultStyle::class, + LineBreakItem::class => DefaultStyle::class, + /** Split item */ + SplitItem::class => DefaultStyle::class, + /** Normal selectable items */ + SelectableItem::class => SelectableStyle::class, + MenuMenuItem::class => SelectableStyle::class, + /** Toggle items */ + CheckboxItem::class => CheckboxStyle::class, + RadioItem::class => RadioStyle::class, + ]; + + /** + * @var array + */ + private $styles; + + public function __construct() + { + $this->styles = [ + DefaultStyle::class => new DefaultStyle(), + SelectableStyle::class => new SelectableStyle(), + CheckboxStyle::class => new CheckboxStyle(), + RadioStyle::class => new RadioStyle() + ]; + } + + /** + * For each of our unmodified item styles, we replace ours with the versions + * from the given style locator. + * + * @param Locator $other + */ + public function importFrom(self $other) : void + { + $this->styles = mapWithKeys( + $this->styles, + function ($styleClass, ItemStyle $instance) use ($other) { + return $instance instanceof Customisable && !$instance->hasChangedFromDefaults() + ? $other->getStyle($styleClass) + : $instance; + } + ); + } + + public function getStyle(string $styleClass) : ItemStyle + { + if (!isset($this->styles[$styleClass])) { + throw InvalidStyle::unregisteredStyle($styleClass); + } + + return $this->styles[$styleClass]; + } + + /** + * TODO: Don't accept $styleClass and figure this ourselves? + * + * @param ItemStyle $itemStyle + * @param string $styleClass + */ + public function setStyle(ItemStyle $itemStyle, string $styleClass) : void + { + if (!isset($this->styles[$styleClass])) { + throw InvalidStyle::unregisteredStyle($styleClass); + } + + if (!$itemStyle instanceof $styleClass) { + //throw + } + + $this->styles[$styleClass] = $itemStyle; + } + + public function getStyleForMenuItem(MenuItemInterface $item) : ItemStyle + { + if (!isset($this->itemStyleMap[get_class($item)])) { + //unregistered menu item + } + + $styleClass = $this->itemStyleMap[get_class($item)]; + + return $this->getStyle($styleClass); + } +} diff --git a/src/Style/RadioStyle.php b/src/Style/RadioStyle.php index 63cb592a..e677e83c 100644 --- a/src/Style/RadioStyle.php +++ b/src/Style/RadioStyle.php @@ -5,7 +5,7 @@ use PhpSchool\CliMenu\MenuItem\MenuItemInterface; use PhpSchool\CliMenu\MenuItem\RadioItem; -class RadioStyle implements ItemStyle +class RadioStyle implements ItemStyle, Customisable { private const DEFAULT_STYLES = [ 'checkedMarker' => '[●] ', diff --git a/src/Style/SelectableStyle.php b/src/Style/SelectableStyle.php index d0224186..401c68f7 100644 --- a/src/Style/SelectableStyle.php +++ b/src/Style/SelectableStyle.php @@ -4,7 +4,7 @@ use PhpSchool\CliMenu\MenuItem\MenuItemInterface; -class SelectableStyle implements ItemStyle +class SelectableStyle implements ItemStyle, Customisable { private const DEFAULT_STYLES = [ 'selectedMarker' => '● ', diff --git a/test/Style/LocatorTest.php b/test/Style/LocatorTest.php new file mode 100644 index 00000000..ac6d951e --- /dev/null +++ b/test/Style/LocatorTest.php @@ -0,0 +1,85 @@ +getStyle(DefaultStyle::class); + $selectableStyle = $locator->getStyle(SelectableStyle::class); + $checkboxStyle = $locator->getStyle(CheckboxStyle::class); + $radioStyle = $locator->getStyle(RadioStyle::class); + + $selectableStyle->setUnselectedMarker('[ ]'); + $selectableStyle->setSelectedMarker('[X]'); + + $checkboxStyle->setCheckedMarker('[*] '); + $radioStyle->setCheckedMarker('[*] '); + + $otherLocator = new Locator(); + + $locator->importFrom($otherLocator); + + self::assertSame($defaultStyle, $locator->getStyle(DefaultStyle::class)); + self::assertSame($selectableStyle, $locator->getStyle(SelectableStyle::class)); + self::assertSame($checkboxStyle, $locator->getStyle(CheckboxStyle::class)); + self::assertSame($radioStyle, $locator->getStyle(RadioStyle::class)); + } + + public function testImportStylesWhenOneStyleNotModified() : void + { + $locator = new Locator(); + + $defaultStyle = $locator->getStyle(DefaultStyle::class); + $selectableStyle = $locator->getStyle(SelectableStyle::class); + $checkboxStyle = $locator->getStyle(CheckboxStyle::class); + $radioStyle = $locator->getStyle(RadioStyle::class); + + $checkboxStyle->setCheckedMarker('[*] '); + $radioStyle->setCheckedMarker('[*] '); + + $otherLocator = new Locator(); + + $locator->importFrom($otherLocator); + + self::assertSame($defaultStyle, $locator->getStyle(DefaultStyle::class)); + self::assertSame($checkboxStyle, $locator->getStyle(CheckboxStyle::class)); + self::assertSame($radioStyle, $locator->getStyle(RadioStyle::class)); + + self::assertNotSame($selectableStyle, $locator->getStyle(SelectableStyle::class)); + self::assertSame($otherLocator->getStyle(SelectableStyle::class), $locator->getStyle(SelectableStyle::class)); + } + + public function testImportStylesWhenStyleNotModified() : void + { + $locator = new Locator(); + + $selectableStyle = $locator->getStyle(SelectableStyle::class); + $checkboxStyle = $locator->getStyle(CheckboxStyle::class); + $radioStyle = $locator->getStyle(RadioStyle::class); + + $otherLocator = new Locator(); + + $locator->importFrom($otherLocator); + + self::assertNotSame($selectableStyle, $locator->getStyle(SelectableStyle::class)); + self::assertNotSame($checkboxStyle, $locator->getStyle(CheckboxStyle::class)); + self::assertNotSame($radioStyle, $locator->getStyle(RadioStyle::class)); + + self::assertSame($otherLocator->getStyle(SelectableStyle::class), $locator->getStyle(SelectableStyle::class)); + self::assertSame($otherLocator->getStyle(CheckboxStyle::class), $locator->getStyle(CheckboxStyle::class)); + self::assertSame($otherLocator->getStyle(RadioStyle::class), $locator->getStyle(RadioStyle::class)); + } +} From 5f072c5c39d6908dde85cc2fef656fe216dd5b2a Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 17:38:23 +0100 Subject: [PATCH 07/13] Fix some type hints --- src/CliMenu.php | 2 +- src/Style/Locator.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CliMenu.php b/src/CliMenu.php index 52059618..260319ff 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -653,7 +653,7 @@ public function setStyle(MenuStyle $style) : void $this->style = $style; } - public function setItemStyle(ItemStyle $style, string $styleClass) + public function setItemStyle(ItemStyle $style, string $styleClass) : void { $this->itemStyleLocator->setStyle($style, $styleClass); } diff --git a/src/Style/Locator.php b/src/Style/Locator.php index 237c5d22..206a04a0 100644 --- a/src/Style/Locator.php +++ b/src/Style/Locator.php @@ -18,6 +18,9 @@ class Locator { + /** + * @var array + */ private $itemStyleMap = [ /** Static non selectable items */ StaticItem::class => DefaultStyle::class, From 132de5c8b87e1f66252cafd70ef3079bca2fd63d Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 18:02:42 +0100 Subject: [PATCH 08/13] Phpstan issues --- phpstan.neon | 4 ---- src/Builder/CliMenuBuilder.php | 16 ++++++++++++---- src/Style/Exception/InvalidStyle.php | 12 ++++++++++++ src/Style/Locator.php | 9 ++------- src/Util/ArrayUtils.php | 6 +++++- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index be37969a..9d52fd98 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,2 @@ parameters: checkMissingIterableValueType: false - ignoreErrors: - - - message: '#Function PhpSchool\\\\CliMenu\\\\Util\\\\mapWithKeys should return array but returns array|false#' - path: src/Util/ArrayUtils.php diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index 834126d5..a90258d7 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -522,7 +522,9 @@ public function build() : CliMenu public function getDefaultStyle() : DefaultStyle { - return $this->menu->getItemStyle(DefaultStyle::class); + $style = $this->menu->getItemStyle(DefaultStyle::class); + assert($style instanceof DefaultStyle); + return $style; } public function setDefaultStyle(DefaultStyle $style) : self @@ -541,7 +543,9 @@ public function modifyDefaultStyle(callable $itemCallable) : self public function getSelectableStyle() : SelectableStyle { - return $this->menu->getItemStyle(SelectableStyle::class); + $style = $this->menu->getItemStyle(SelectableStyle::class); + assert($style instanceof SelectableStyle); + return $style; } public function setSelectableStyle(SelectableStyle $style) : self @@ -560,7 +564,9 @@ public function modifySelectableStyle(callable $itemCallable) : self public function getCheckboxStyle() : CheckboxStyle { - return $this->menu->getItemStyle(CheckboxStyle::class); + $style = $this->menu->getItemStyle(CheckboxStyle::class); + assert($style instanceof CheckboxStyle); + return $style; } public function setCheckboxStyle(CheckboxStyle $style) : self @@ -579,7 +585,9 @@ public function modifyCheckboxStyle(callable $itemCallable) : self public function getRadioStyle() : RadioStyle { - return $this->menu->getItemStyle(RadioItem::class); + $style = $this->menu->getItemStyle(RadioStyle::class); + assert($style instanceof RadioStyle); + return $style; } public function setRadioStyle(RadioStyle $style) : self diff --git a/src/Style/Exception/InvalidStyle.php b/src/Style/Exception/InvalidStyle.php index 2c86d007..e5f7c166 100644 --- a/src/Style/Exception/InvalidStyle.php +++ b/src/Style/Exception/InvalidStyle.php @@ -4,10 +4,22 @@ namespace PhpSchool\CliMenu\Style\Exception; +use PhpSchool\CliMenu\MenuItem\MenuItemInterface; + class InvalidStyle extends \RuntimeException { public static function unregisteredStyle(string $styleClass) : self { return new self("Style class: '$styleClass' is not registered"); } + + public static function notSubClassOf(string $styleClass) : self + { + return new self("Style instance must be a subclass of: '$styleClass'"); + } + + public static function unregisteredItem(string $itemClass) : self + { + return new self("Menu item: '$itemClass' does not have a registered style class"); + } } diff --git a/src/Style/Locator.php b/src/Style/Locator.php index 206a04a0..839ba924 100644 --- a/src/Style/Locator.php +++ b/src/Style/Locator.php @@ -22,16 +22,12 @@ class Locator * @var array */ private $itemStyleMap = [ - /** Static non selectable items */ StaticItem::class => DefaultStyle::class, AsciiArtItem::class => DefaultStyle::class, LineBreakItem::class => DefaultStyle::class, - /** Split item */ SplitItem::class => DefaultStyle::class, - /** Normal selectable items */ SelectableItem::class => SelectableStyle::class, MenuMenuItem::class => SelectableStyle::class, - /** Toggle items */ CheckboxItem::class => CheckboxStyle::class, RadioItem::class => RadioStyle::class, ]; @@ -79,7 +75,6 @@ public function getStyle(string $styleClass) : ItemStyle } /** - * TODO: Don't accept $styleClass and figure this ourselves? * * @param ItemStyle $itemStyle * @param string $styleClass @@ -91,7 +86,7 @@ public function setStyle(ItemStyle $itemStyle, string $styleClass) : void } if (!$itemStyle instanceof $styleClass) { - //throw + throw InvalidStyle::notSubClassOf($styleClass); } $this->styles[$styleClass] = $itemStyle; @@ -100,7 +95,7 @@ public function setStyle(ItemStyle $itemStyle, string $styleClass) : void public function getStyleForMenuItem(MenuItemInterface $item) : ItemStyle { if (!isset($this->itemStyleMap[get_class($item)])) { - //unregistered menu item + throw InvalidStyle::unregisteredItem(get_class($item)); } $styleClass = $this->itemStyleMap[get_class($item)]; diff --git a/src/Util/ArrayUtils.php b/src/Util/ArrayUtils.php index d407f73d..f51cc90c 100644 --- a/src/Util/ArrayUtils.php +++ b/src/Util/ArrayUtils.php @@ -6,10 +6,14 @@ function mapWithKeys(array $array, callable $callback) : array { - return array_combine( + $arr = array_combine( array_keys($array), array_map($callback, array_keys($array), $array) ); + + assert(is_array($arr)); + + return $arr; } function each(array $array, callable $callback) : void From 8b71c34447302ee93532de9ba740d75d10978d8f Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 18:39:28 +0100 Subject: [PATCH 09/13] Refactor propagate styles --- src/Builder/CliMenuBuilder.php | 41 +++++++++++++++++++++++----------- src/Style/CheckboxStyle.php | 2 +- src/Style/Customisable.php | 10 --------- src/Style/DefaultStyle.php | 2 +- src/Style/ItemStyle.php | 2 ++ src/Style/Locator.php | 11 +++------ src/Style/RadioStyle.php | 2 +- src/Style/SelectableStyle.php | 2 +- 8 files changed, 37 insertions(+), 35 deletions(-) delete mode 100644 src/Style/Customisable.php diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index a90258d7..888d7056 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -17,11 +17,13 @@ use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\Customisable; use PhpSchool\CliMenu\Style\DefaultStyle; use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\Terminal\Terminal; +use function PhpSchool\CliMenu\Util\each; /** * @author Michael Woodward @@ -611,24 +613,37 @@ public function modifyRadioStyle(callable $itemCallable) : self private function propagateStyles(CliMenu $menu, array $items = []) : void { $currentItems = !empty($items) ? $items : $menu->getItems(); - - foreach ($currentItems as $item) { - if (!$item->getStyle()->hasChangedFromDefaults()) { + //apply menu items styles to items, if they have not changed + each( + array_filter($currentItems, function (MenuItemInterface $item) { + return !$item->getStyle()->hasChangedFromDefaults(); + }), + function ($index, $item) use ($menu) { $item->setStyle(clone $menu->getItemStyleForItem($item)); } - - // Apply current style to children, if they are not customized - if ($item instanceof MenuMenuItem) { - $subMenu = $item->getSubMenu(); - + ); + + //push current menu item styles to sub menus + each( + array_filter($currentItems, function (MenuItemInterface $item) { + return $item instanceof MenuMenuItem; + }), + function ($index, MenuMenuItem $menuItem) use ($menu) { + $subMenu = $menuItem->getSubMenu(); $subMenu->importStyles($menu); + $this->propagateStyles($subMenu); } - - // Apply styles to SplitItem children using current $menu - if ($item instanceof SplitItem) { - $this->propagateStyles($menu, $item->getItems()); + ); + + //push current menu item styles to SplitItem children using current $menu + each( + array_filter($currentItems, function (MenuItemInterface $item) { + return $item instanceof SplitItem; + }), + function ($index, SplitItem $splitItem) use ($menu) { + $this->propagateStyles($menu, $splitItem->getItems()); } - } + ); } } diff --git a/src/Style/CheckboxStyle.php b/src/Style/CheckboxStyle.php index 0d3030cf..ed9a6ae4 100644 --- a/src/Style/CheckboxStyle.php +++ b/src/Style/CheckboxStyle.php @@ -5,7 +5,7 @@ use PhpSchool\CliMenu\MenuItem\CheckboxItem; use PhpSchool\CliMenu\MenuItem\MenuItemInterface; -class CheckboxStyle implements ItemStyle, Customisable +class CheckboxStyle implements ItemStyle { private const DEFAULT_STYLES = [ 'checkedMarker' => '[✔] ', diff --git a/src/Style/Customisable.php b/src/Style/Customisable.php deleted file mode 100644 index 4775c68c..00000000 --- a/src/Style/Customisable.php +++ /dev/null @@ -1,10 +0,0 @@ -styles = mapWithKeys( $this->styles, function ($styleClass, ItemStyle $instance) use ($other) { - return $instance instanceof Customisable && !$instance->hasChangedFromDefaults() - ? $other->getStyle($styleClass) - : $instance; + return $instance->hasChangedFromDefaults() + ? $instance + : $other->getStyle($styleClass); } ); } @@ -74,11 +74,6 @@ public function getStyle(string $styleClass) : ItemStyle return $this->styles[$styleClass]; } - /** - * - * @param ItemStyle $itemStyle - * @param string $styleClass - */ public function setStyle(ItemStyle $itemStyle, string $styleClass) : void { if (!isset($this->styles[$styleClass])) { diff --git a/src/Style/RadioStyle.php b/src/Style/RadioStyle.php index e677e83c..63cb592a 100644 --- a/src/Style/RadioStyle.php +++ b/src/Style/RadioStyle.php @@ -5,7 +5,7 @@ use PhpSchool\CliMenu\MenuItem\MenuItemInterface; use PhpSchool\CliMenu\MenuItem\RadioItem; -class RadioStyle implements ItemStyle, Customisable +class RadioStyle implements ItemStyle { private const DEFAULT_STYLES = [ 'checkedMarker' => '[●] ', diff --git a/src/Style/SelectableStyle.php b/src/Style/SelectableStyle.php index 401c68f7..d0224186 100644 --- a/src/Style/SelectableStyle.php +++ b/src/Style/SelectableStyle.php @@ -4,7 +4,7 @@ use PhpSchool\CliMenu\MenuItem\MenuItemInterface; -class SelectableStyle implements ItemStyle, Customisable +class SelectableStyle implements ItemStyle { private const DEFAULT_STYLES = [ 'selectedMarker' => '● ', From 91d14aab56b99abf81d2428e51dab1bf0c22d710 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 19:03:26 +0100 Subject: [PATCH 10/13] Delegate style propagation --- src/Builder/CliMenuBuilder.php | 43 +------------------------------ src/CliMenu.php | 27 ++++++++++++++++--- src/MenuItem/MenuMenuItem.php | 11 +++++++- src/MenuItem/PropagatesStyles.php | 16 ++++++++++++ src/MenuItem/SplitItem.php | 28 +++++++++++++++++++- 5 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 src/MenuItem/PropagatesStyles.php diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index 888d7056..181574bb 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -516,7 +516,7 @@ public function build() : CliMenu } if (!$this->subMenu) { - $this->propagateStyles($this->menu); + $this->menu->propagateStyles(); } return $this->menu; @@ -605,45 +605,4 @@ public function modifyRadioStyle(callable $itemCallable) : self return $this; } - - /** - * Pass styles from current menu to sub-menu - * only if sub-menu style has not be customized - */ - private function propagateStyles(CliMenu $menu, array $items = []) : void - { - $currentItems = !empty($items) ? $items : $menu->getItems(); - //apply menu items styles to items, if they have not changed - each( - array_filter($currentItems, function (MenuItemInterface $item) { - return !$item->getStyle()->hasChangedFromDefaults(); - }), - function ($index, $item) use ($menu) { - $item->setStyle(clone $menu->getItemStyleForItem($item)); - } - ); - - //push current menu item styles to sub menus - each( - array_filter($currentItems, function (MenuItemInterface $item) { - return $item instanceof MenuMenuItem; - }), - function ($index, MenuMenuItem $menuItem) use ($menu) { - $subMenu = $menuItem->getSubMenu(); - $subMenu->importStyles($menu); - - $this->propagateStyles($subMenu); - } - ); - - //push current menu item styles to SplitItem children using current $menu - each( - array_filter($currentItems, function (MenuItemInterface $item) { - return $item instanceof SplitItem; - }), - function ($index, SplitItem $splitItem) use ($menu) { - $this->propagateStyles($menu, $splitItem->getItems()); - } - ); - } } diff --git a/src/CliMenu.php b/src/CliMenu.php index 260319ff..1a0bea7a 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -10,21 +10,19 @@ use PhpSchool\CliMenu\Input\Text; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuItemInterface; +use PhpSchool\CliMenu\MenuItem\PropagatesStyles; use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\Dialogue\Confirm; use PhpSchool\CliMenu\Dialogue\Flash; -use PhpSchool\CliMenu\Style\CheckboxStyle; -use PhpSchool\CliMenu\Style\DefaultStyle; use PhpSchool\CliMenu\Style\ItemStyle; use PhpSchool\CliMenu\Style\Locator; -use PhpSchool\CliMenu\Style\RadioStyle; -use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\CliMenu\Util\StringUtil as s; use PhpSchool\Terminal\InputCharacter; use PhpSchool\Terminal\NonCanonicalReader; use PhpSchool\Terminal\Terminal; +use function PhpSchool\CliMenu\Util\each; /** * @author Michael Woodward @@ -743,4 +741,25 @@ private function guardSingleLine(string $text) : void throw new \InvalidArgumentException; } } + + public function propagateStyles() : void + { + each( + array_filter($this->items, function (MenuItemInterface $item) { + return !$item->getStyle()->hasChangedFromDefaults(); + }), + function (int $index, $item) { + $item->setStyle(clone $this->getItemStyleForItem($item)); + } + ); + + each( + array_filter($this->items, function (MenuItemInterface $item) { + return $item instanceof PropagatesStyles; + }), + function (int $index, PropagatesStyles $item) { + $item->propagateStyles($this); + } + ); + } } diff --git a/src/MenuItem/MenuMenuItem.php b/src/MenuItem/MenuMenuItem.php index cd3dfe6d..8d0c1bb7 100644 --- a/src/MenuItem/MenuMenuItem.php +++ b/src/MenuItem/MenuMenuItem.php @@ -12,7 +12,7 @@ /** * @author Michael Woodward */ -class MenuMenuItem implements MenuItemInterface +class MenuMenuItem implements MenuItemInterface, PropagatesStyles { /** * @var string @@ -176,4 +176,13 @@ public function setStyle(SelectableStyle $style) : void { $this->style = $style; } + + /** + * @inheritDoc + */ + public function propagateStyles(CliMenu $parent): void + { + $this->getSubMenu()->importStyles($parent); + $this->getSubMenu()->propagateStyles(); + } } diff --git a/src/MenuItem/PropagatesStyles.php b/src/MenuItem/PropagatesStyles.php new file mode 100644 index 00000000..5c412069 --- /dev/null +++ b/src/MenuItem/PropagatesStyles.php @@ -0,0 +1,16 @@ + */ -class SplitItem implements MenuItemInterface +class SplitItem implements MenuItemInterface, PropagatesStyles { /** * @var array @@ -359,4 +361,28 @@ public function setStyle(DefaultStyle $style): void { $this->style = $style; } + + /** + * @inheritDoc + */ + public function propagateStyles(CliMenu $parent): void + { + each( + array_filter($this->getItems(), function (MenuItemInterface $item) { + return !$item->getStyle()->hasChangedFromDefaults(); + }), + function ($index, $item) use ($parent) { + $item->setStyle(clone $parent->getItemStyleForItem($item)); + } + ); + + each( + array_filter($this->getItems(), function (MenuItemInterface $item) { + return $item instanceof PropagatesStyles; + }), + function ($index, PropagatesStyles $item) use ($parent) { + $item->propagateStyles($parent); + } + ); + } } From 9c1b905bb2226d01eacbed436d9b6c191364cbb7 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 19:07:05 +0100 Subject: [PATCH 11/13] Remove unused imports --- src/Builder/CliMenuBuilder.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index 181574bb..867f7859 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -17,13 +17,11 @@ use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; use PhpSchool\CliMenu\Style\CheckboxStyle; -use PhpSchool\CliMenu\Style\Customisable; use PhpSchool\CliMenu\Style\DefaultStyle; use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\Terminal\Terminal; -use function PhpSchool\CliMenu\Util\each; /** * @author Michael Woodward From 24e4f9f78252d9641d3a4d876f9b2355fcb57edb Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 19:17:30 +0100 Subject: [PATCH 12/13] DefaultStyle tests --- src/Style/DefaultStyle.php | 2 +- test/Style/DefaultStyleTest.php | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/Style/DefaultStyleTest.php diff --git a/src/Style/DefaultStyle.php b/src/Style/DefaultStyle.php index 84a6e8d1..cd751a1c 100644 --- a/src/Style/DefaultStyle.php +++ b/src/Style/DefaultStyle.php @@ -26,7 +26,7 @@ public function getItemExtra() : string return ''; } - public function getMarker(MenuItemInterface $style, bool $isSelected) : string + public function getMarker(MenuItemInterface $item, bool $isSelected) : string { return ''; } diff --git a/test/Style/DefaultStyleTest.php b/test/Style/DefaultStyleTest.php new file mode 100644 index 00000000..7ec6b069 --- /dev/null +++ b/test/Style/DefaultStyleTest.php @@ -0,0 +1,41 @@ +hasChangedFromDefaults()); + } + + public function testGetMarker() : void + { + $item = new LineBreakItem('X'); + $style = new DefaultStyle; + + self::assertSame('', $style->getMarker($item, false)); + self::assertSame('', $style->getMarker($item, true)); + } + + public function testGetSetItemExtra() : void + { + $style = new DefaultStyle; + + self::assertSame('', $style->getItemExtra()); + } + + + public function testGetSetDisplayExtra() : void + { + $style = new DefaultStyle; + + self::assertFalse($style->getDisplaysExtra()); + } +} From 66392cbd76b92362a70dabcdddbe447221486084 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 15 Feb 2020 19:47:20 +0100 Subject: [PATCH 13/13] Locator tests --- test/Style/LocatorTest.php | 104 +++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/test/Style/LocatorTest.php b/test/Style/LocatorTest.php index ac6d951e..8d4d957a 100644 --- a/test/Style/LocatorTest.php +++ b/test/Style/LocatorTest.php @@ -4,8 +4,18 @@ namespace PhpSchool\CliMenuTest\Style; +use PhpSchool\CliMenu\CliMenu; +use PhpSchool\CliMenu\MenuItem\AsciiArtItem; +use PhpSchool\CliMenu\MenuItem\CheckboxItem; +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\MenuItem\StaticItem; use PhpSchool\CliMenu\Style\CheckboxStyle; use PhpSchool\CliMenu\Style\DefaultStyle; +use PhpSchool\CliMenu\Style\Exception\InvalidStyle; use PhpSchool\CliMenu\Style\Locator; use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Style\SelectableStyle; @@ -82,4 +92,98 @@ public function testImportStylesWhenStyleNotModified() : void self::assertSame($otherLocator->getStyle(CheckboxStyle::class), $locator->getStyle(CheckboxStyle::class)); self::assertSame($otherLocator->getStyle(RadioStyle::class), $locator->getStyle(RadioStyle::class)); } + + public function testGetStyleForMenuItemThrowsExceptionIfItemNotRegistered() : void + { + self::expectException(InvalidStyle::class); + + $myItem = new class extends LineBreakItem { + }; + + $locator = new Locator(); + $locator->getStyleForMenuItem($myItem); + } + + public function itemStyleProvider() : array + { + $action = function () { + }; + + return [ + [DefaultStyle::class, new LineBreakItem()], + [DefaultStyle::class, new StaticItem('*')], + [DefaultStyle::class, new AsciiArtItem('*')], + [SelectableStyle::class, new SelectableItem('1', $action)], + [SelectableStyle::class, new MenuMenuItem('2', new CliMenu('sub', []))], + [CheckboxStyle::class, new CheckboxItem('3', $action)], + [RadioStyle::class, new RadioItem('4', $action)], + ]; + } + + /** + * @dataProvider itemStyleProvider + */ + public function testGetStyleForMenuItem(string $expectedStyleClass, MenuItemInterface $menuItem) : void + { + $locator = new Locator(); + + self::assertInstanceOf($expectedStyleClass, $locator->getStyleForMenuItem($menuItem)); + } + + public function testGetStyleThrowsExceptionIfStyleClassNotRegistered() : void + { + self::expectException(InvalidStyle::class); + + $locator = new Locator(); + $locator->getStyle('NonExistingStyleClass'); + } + + public function styleProvider() : array + { + return [ + [DefaultStyle::class], + [SelectableStyle::class], + [SelectableStyle::class], + [CheckboxStyle::class], + [RadioStyle::class], + ]; + } + + /** + * @dataProvider styleProvider + */ + public function testGetStyle(string $styleClass) : void + { + $locator = new Locator(); + + self::assertInstanceOf($styleClass, $locator->getStyle($styleClass)); + } + + public function testSetStyleThrowsExceptionIfStyleClassNotRegistered() : void + { + self::expectException(InvalidStyle::class); + + $locator = new Locator(); + $locator->setStyle(new DefaultStyle(), 'NonExistingStyleClass'); + } + + public function testSetStyleThrowsExceptionIfStyleNotInstanceOfStyleClass() : void + { + self::expectException(InvalidStyle::class); + + $invalidStyle = new class extends SelectableStyle { + }; + + $locator = new Locator(); + $locator->setStyle($invalidStyle, DefaultStyle::class); + } + + public function testSetStyle() : void + { + $locator = new Locator(); + + $locator->setStyle($new = new DefaultStyle(), DefaultStyle::class); + + self::assertSame($new, $locator->getStyle(DefaultStyle::class)); + } }