Skip to content

Commit 61c6940

Browse files
authored
Merge pull request #138 from php-school/split-item-select-refactor
Refactor split item item selection and CliMenu can select checks.
2 parents b3b5927 + d293834 commit 61c6940

9 files changed

+510
-8
lines changed

src/CliMenu.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ protected function moveSelectionVertically(string $direction) : void
289289
? end($itemKeys)
290290
: reset($itemKeys);
291291
}
292-
} while (!$this->getSelectedItem()->canSelect());
292+
} while (!$this->canSelect());
293293
}
294294

295295
/**
@@ -301,6 +301,7 @@ protected function moveSelectionHorizontally(string $direction) : void
301301
return;
302302
}
303303

304+
/** @var SplitItem $item */
304305
$item = $this->items[$this->selectedItem];
305306
$itemKeys = array_keys($item->getItems());
306307
$selectedItemIndex = $item->getSelectedItemIndex();
@@ -309,17 +310,37 @@ protected function moveSelectionHorizontally(string $direction) : void
309310
$direction === 'LEFT'
310311
? $selectedItemIndex--
311312
: $selectedItemIndex++;
312-
$item->setSelectedItemIndex($selectedItemIndex);
313313

314314
if (!array_key_exists($selectedItemIndex, $item->getItems())) {
315315
$selectedItemIndex = $direction === 'LEFT'
316316
? end($itemKeys)
317317
: reset($itemKeys);
318-
$item->setSelectedItemIndex($selectedItemIndex);
319318
}
320-
} while (!$item->getSelectedItem()->canSelect());
319+
} while (!$item->canSelectIndex($selectedItemIndex));
320+
321+
$item->setSelectedItemIndex($selectedItemIndex);
321322
}
322323

324+
/**
325+
* Can the currently selected item actually be selected?
326+
*
327+
* For example:
328+
* selectable item -> yes
329+
* static item -> no
330+
* split item with only static items -> no
331+
* split item with at least one selectable item -> yes
332+
*
333+
* @return bool
334+
*/
335+
private function canSelect() : bool
336+
{
337+
return $this->items[$this->selectedItem]->canSelect();
338+
}
339+
340+
/**
341+
* Retrieve the item the user actually selected
342+
*
343+
*/
323344
public function getSelectedItem() : MenuItemInterface
324345
{
325346
$item = $this->items[$this->selectedItem];

src/MenuItem/SplitItem.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,19 @@ private function buildCell(
200200
}, $content, array_keys($content));
201201
}
202202

203+
/**
204+
* Is there an item with this index and can it be
205+
* selected?
206+
*/
207+
public function canSelectIndex(int $index) : bool
208+
{
209+
return isset($this->items[$index]) && $this->items[$index]->canSelect();
210+
}
211+
212+
/**
213+
* Set the item index which should be selected. If the item does
214+
* not exist then throw an exception.
215+
*/
203216
public function setSelectedItemIndex(int $index) : void
204217
{
205218
if (!isset($this->items[$index])) {
@@ -209,16 +222,26 @@ public function setSelectedItemIndex(int $index) : void
209222
$this->selectedItemIndex = $index;
210223
}
211224

225+
/**
226+
* Get the currently select item index.
227+
* May be null in case of no selectable item.
228+
*/
212229
public function getSelectedItemIndex() : ?int
213230
{
214231
return $this->selectedItemIndex;
215232
}
216233

234+
/**
235+
* Get the currently selected item - if no items are selectable
236+
* then throw an exception.
237+
*/
217238
public function getSelectedItem() : MenuItemInterface
218239
{
219-
return $this->selectedItemIndex !== null
220-
? $this->items[$this->selectedItemIndex]
221-
: $this;
240+
if (null === $this->selectedItemIndex) {
241+
throw new \RuntimeException('No item is selected');
242+
}
243+
244+
return $this->items[$this->selectedItemIndex];
222245
}
223246

224247
public function getItems() : array

test/CliMenuTest.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use PhpSchool\CliMenu\Exception\MenuNotOpenException;
77
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
88
use PhpSchool\CliMenu\MenuItem\SelectableItem;
9+
use PhpSchool\CliMenu\MenuItem\SplitItem;
10+
use PhpSchool\CliMenu\MenuItem\StaticItem;
911
use PhpSchool\CliMenu\MenuStyle;
1012
use PhpSchool\Terminal\Terminal;
1113
use PhpSchool\Terminal\UnixTerminal;
@@ -667,6 +669,171 @@ public function testRemoveCustomControlMapping() : void
667669
self::assertSame([], $this->readAttribute($menu, 'customControlMappings'));
668670
}
669671

672+
public function testSplitItemWithNoSelectableItemsScrollingVertically() : void
673+
{
674+
$this->terminal->expects($this->exactly(3))
675+
->method('read')
676+
->willReturn("\033[B", "\033[B", "\n");
677+
678+
$action = function (CliMenu $menu) {
679+
$menu->close();
680+
};
681+
682+
$menu = new CliMenu('PHP School FTW', [], $this->terminal);
683+
$menu->addItem(new SelectableItem('One', $action));
684+
$menu->addItem(new SplitItem([new StaticItem('Two'), new StaticItem('Three')]));
685+
$menu->addItem(new SelectableItem('Four', $action));
686+
687+
$menu->open();
688+
689+
self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
690+
}
691+
692+
public function testSplitItemWithSelectableItemsScrollingVertical() : void
693+
{
694+
$this->terminal->expects($this->exactly(4))
695+
->method('read')
696+
->willReturn("\033[B", "\033[B", "\033[B", "\n");
697+
698+
$action = function (CliMenu $menu) {
699+
$menu->close();
700+
};
701+
702+
$splitAction = function (CliMenu $menu) {
703+
};
704+
705+
$menu = new CliMenu('PHP School FTW', [], $this->terminal);
706+
$menu->addItem(new SelectableItem('One', $action));
707+
$menu->addItem(
708+
new SplitItem(
709+
[new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)]
710+
)
711+
);
712+
$menu->addItem(new SelectableItem('Four', $action));
713+
714+
$menu->open();
715+
716+
self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
717+
}
718+
719+
public function testSplitItemWithSelectableItemsScrollingRight() : void
720+
{
721+
$this->terminal->expects($this->exactly(6))
722+
->method('read')
723+
->willReturn("\033[B", "\033[C", "\033[C", "\033[C", "\033[B", "\n");
724+
725+
$action = function (CliMenu $menu) {
726+
$menu->close();
727+
};
728+
729+
$splitAction = function (CliMenu $menu) {
730+
};
731+
732+
$menu = new CliMenu('PHP School FTW', [], $this->terminal);
733+
$menu->addItem(new SelectableItem('One', $action));
734+
$menu->addItem(
735+
new SplitItem(
736+
[new SelectableItem('Two', $splitAction), new SelectableItem('Three', $splitAction)]
737+
)
738+
);
739+
$menu->addItem(new SelectableItem('Four', $action));
740+
741+
$menu->open();
742+
743+
self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
744+
}
745+
746+
public function testSplitItemWithSelectableItemsScrollingLeft() : void
747+
{
748+
$this->terminal->expects($this->exactly(6))
749+
->method('read')
750+
->willReturn("\033[B", "\033[D", "\033[D", "\033[D", "\033[B", "\n");
751+
752+
$action = function (CliMenu $menu) {
753+
$menu->close();
754+
};
755+
756+
$splitAction = function (CliMenu $menu) {
757+
};
758+
759+
$menu = new CliMenu('PHP School FTW', [], $this->terminal);
760+
$menu->addItem(new SelectableItem('One', $action));
761+
$menu->addItem(
762+
new SplitItem(
763+
[
764+
new SelectableItem('Two', $splitAction),
765+
new SelectableItem('Three', $splitAction),
766+
new SelectableItem('Four', $splitAction),
767+
]
768+
)
769+
);
770+
$menu->addItem(new SelectableItem('Five', $action));
771+
772+
$menu->open();
773+
774+
self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
775+
}
776+
777+
public function testSplitItemWithSelectableAndStaticItemsScrollingHorizontally() : void
778+
{
779+
$this->terminal->expects($this->exactly(6))
780+
->method('read')
781+
->willReturn("\033[B", "\033[D", "\033[D", "\033[D", "\033[B", "\n");
782+
783+
$action = function (CliMenu $menu) {
784+
$menu->close();
785+
};
786+
787+
$splitAction = function (CliMenu $menu) {
788+
};
789+
790+
$menu = new CliMenu('PHP School FTW', [], $this->terminal);
791+
$menu->addItem(new SelectableItem('One', $action));
792+
$menu->addItem(
793+
new SplitItem(
794+
[
795+
new SelectableItem('Two', $splitAction),
796+
new StaticItem('Three'),
797+
new SelectableItem('Four', $splitAction),
798+
]
799+
)
800+
);
801+
$menu->addItem(new SelectableItem('Five', $action));
802+
803+
$menu->open();
804+
805+
self::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
806+
}
807+
808+
809+
public function testSelectableCallableReceivesSelectableAndNotSplitItem() : void
810+
{
811+
$this->terminal->expects($this->exactly(1))
812+
->method('read')
813+
->willReturn("\n");
814+
815+
$actualSelectedItem = null;
816+
$action = function (CliMenu $menu) use (&$actualSelectedItem) {
817+
$actualSelectedItem = $menu->getSelectedItem();
818+
$menu->close();
819+
};
820+
821+
$expectedSelectedItem = new SelectableItem('Two', $action);
822+
$menu = new CliMenu('PHP School FTW', [], $this->terminal);
823+
$menu->addItem(
824+
new SplitItem(
825+
[
826+
$expectedSelectedItem,
827+
new StaticItem('Three'),
828+
new SelectableItem('Four', $action),
829+
]
830+
)
831+
);
832+
$menu->open();
833+
834+
self::assertSame($expectedSelectedItem, $actualSelectedItem);
835+
}
836+
670837
private function getTestFile() : string
671838
{
672839
return sprintf('%s/res/%s.txt', __DIR__, $this->getName());

test/MenuItem/SplitItemTest.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,27 @@ public function testGetSelectedItemReturnsItem() : void
431431
self::assertSame($item2, $splitItem->getSelectedItem());
432432
}
433433

434-
public function testGetSelectedItemReturnsSplitItemWhenNoSelectableItemExists() : void
434+
public function testGetSelectedItemThrowsExceptionWhenNoSelectableItemExists() : void
435435
{
436+
self::expectException(\RuntimeException::class);
437+
self::expectExceptionMessage('No item is selected');
438+
436439
$item1 = new StaticItem('One');
437440

438441
$splitItem = new SplitItem([$item1]);
439442
self::assertSame($splitItem, $splitItem->getSelectedItem());
440443
}
444+
445+
public function testCanSelectIndex() : void
446+
{
447+
$item1 = new StaticItem('One');
448+
$item2 = new SelectableItem('Two', function () {
449+
});
450+
451+
$splitItem = new SplitItem([$item1, $item2]);
452+
453+
self::assertFalse($splitItem->canSelectIndex(0));
454+
self::assertFalse($splitItem->canSelectIndex(5));
455+
self::assertTrue($splitItem->canSelectIndex(1));
456+
}
441457
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
3+
 
4+
 PHP School FTW 
5+
 ========================================== 
6+
 ● One 
7+
 Two Three 
8+
 ○ Four 
9+
 
10+
11+
12+
13+
14+
 
15+
 PHP School FTW 
16+
 ========================================== 
17+
 ○ One 
18+
 Two Three 
19+
 ● Four 
20+
 
21+
22+
23+
24+
25+
 
26+
 PHP School FTW 
27+
 ========================================== 
28+
 ● One 
29+
 Two Three 
30+
 ○ Four 
31+
 
32+
33+

0 commit comments

Comments
 (0)