Skip to content

Commit 21a41e4

Browse files
committed
Apply fixed Coderabbitai review.
1 parent 2cf595d commit 21a41e4

4 files changed

Lines changed: 282 additions & 9 deletions

File tree

‎src/Exception/PanelViewMessage.php‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ enum PanelViewMessage: string
2727
*/
2828
case COLUMN_STYLE_KEY_INVALID = 'Debug panel column styles must be keyed by an existing column index.';
2929

30+
/**
31+
* Indicates that an entry passed to a composite block was not built by the matching factory.
32+
*
33+
* Format: "Debug panel %s entries must be built with PanelView::%s()."
34+
*/
35+
case ENTRY_INVALID = 'Debug panel %s entries must be built with PanelView::%s().';
36+
3037
/**
3138
* Indicates that an inline value is neither a scalar, `null`, nor a shape this class produces.
3239
*

‎src/PanelView.php‎

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,23 @@ public static function fact(string $label, string $value): array
223223
/**
224224
* Appends a compact strip of label and value pairs.
225225
*
226-
* @param FactEntry ...$facts Entries produced by {@see self::fact()}.
226+
* @param array<array-key, mixed> ...$facts Entries produced by {@see self::fact()}.
227+
*
228+
* @throws InvalidArgumentException if an argument was not built by {@see self::fact()}.
227229
*
228230
* @return self New view with the fact strip appended.
229231
*/
230232
public function facts(array ...$facts): self
231233
{
232-
return $this->append(['kind' => 'facts', 'facts' => array_values($facts)]);
234+
$entries = [];
235+
236+
foreach ($facts as $fact) {
237+
self::assertFactEntry($fact);
238+
239+
$entries[] = $fact;
240+
}
241+
242+
return $this->append(['kind' => 'facts', 'facts' => $entries]);
233243
}
234244

235245
/**
@@ -311,13 +321,23 @@ public static function link(string $label, string $href, bool $external = false)
311321
* Appends a vendor-grouped package manifest.
312322
*
313323
* @param string $label Vendor prefix the packages share, shown as the group heading.
314-
* @param PackageEntry ...$packages Entries produced by {@see self::package()}.
324+
* @param array<array-key, mixed> ...$packages Entries produced by {@see self::package()}.
325+
*
326+
* @throws InvalidArgumentException if an argument was not built by {@see self::package()}.
315327
*
316328
* @return self New view with the manifest appended.
317329
*/
318330
public function manifest(string $label, array ...$packages): self
319331
{
320-
return $this->append(['kind' => 'manifest', 'label' => $label, 'packages' => array_values($packages)]);
332+
$entries = [];
333+
334+
foreach ($packages as $package) {
335+
self::assertPackageEntry($package);
336+
337+
$entries[] = $package;
338+
}
339+
340+
return $this->append(['kind' => 'manifest', 'label' => $label, 'packages' => $entries]);
321341
}
322342

323343
/**
@@ -396,13 +416,23 @@ public static function pill(string $label, string $state, bool $enabled): array
396416
/**
397417
* Appends a strip of status pills.
398418
*
399-
* @param PillEntry ...$pills Entries produced by {@see self::pill()}.
419+
* @param array<array-key, mixed> ...$pills Entries produced by {@see self::pill()}.
420+
*
421+
* @throws InvalidArgumentException if an argument was not built by {@see self::pill()}.
400422
*
401423
* @return self New view with the pill strip appended.
402424
*/
403425
public function pills(array ...$pills): self
404426
{
405-
return $this->append(['kind' => 'pills', 'pills' => array_values($pills)]);
427+
$entries = [];
428+
429+
foreach ($pills as $pill) {
430+
self::assertPillEntry($pill);
431+
432+
$entries[] = $pill;
433+
}
434+
435+
return $this->append(['kind' => 'pills', 'pills' => $entries]);
406436
}
407437

408438
/**
@@ -443,13 +473,23 @@ public static function readout(string $label, string $value, string $caption = '
443473
/**
444474
* Appends a row of headline readout cards.
445475
*
446-
* @param ReadoutEntry ...$readouts Entries produced by {@see self::readout()}.
476+
* @param array<array-key, mixed> ...$readouts Entries produced by {@see self::readout()}.
477+
*
478+
* @throws InvalidArgumentException if an argument was not built by {@see self::readout()}.
447479
*
448480
* @return self New view with the readout row appended.
449481
*/
450482
public function readouts(array ...$readouts): self
451483
{
452-
return $this->append(['kind' => 'readouts', 'readouts' => array_values($readouts)]);
484+
$entries = [];
485+
486+
foreach ($readouts as $readout) {
487+
self::assertReadoutEntry($readout);
488+
489+
$entries[] = $readout;
490+
}
491+
492+
return $this->append(['kind' => 'readouts', 'readouts' => $entries]);
453493
}
454494

455495
/**
@@ -681,6 +721,96 @@ private function append(array $block): self
681721
return new self($this->summary, [...$this->blocks, $block], $this->toolbar, $this->active);
682722
}
683723

724+
/**
725+
* Asserts that an entry was built by {@see self::fact()}.
726+
*
727+
* @param array<array-key, mixed> $entry Entry passed to {@see self::facts()}.
728+
*
729+
* @throws InvalidArgumentException if the entry does not carry the shape the factory produces.
730+
*
731+
* @phpstan-assert FactEntry $entry
732+
*/
733+
private static function assertFactEntry(array $entry): void
734+
{
735+
if (
736+
($entry['kind'] ?? null) !== 'fact'
737+
|| is_string($entry['label'] ?? null) === false
738+
|| is_string($entry['value'] ?? null) === false
739+
) {
740+
throw new InvalidArgumentException(
741+
PanelViewMessage::ENTRY_INVALID->getMessage('fact', 'fact'),
742+
);
743+
}
744+
}
745+
746+
/**
747+
* Asserts that an entry was built by {@see self::package()}.
748+
*
749+
* @param array<array-key, mixed> $entry Entry passed to {@see self::manifest()}.
750+
*
751+
* @throws InvalidArgumentException if the entry does not carry the shape the factory produces.
752+
*
753+
* @phpstan-assert PackageEntry $entry
754+
*/
755+
private static function assertPackageEntry(array $entry): void
756+
{
757+
if (
758+
($entry['kind'] ?? null) !== 'package'
759+
|| is_string($entry['name'] ?? null) === false
760+
|| is_string($entry['version'] ?? null) === false
761+
) {
762+
throw new InvalidArgumentException(
763+
PanelViewMessage::ENTRY_INVALID->getMessage('package', 'package'),
764+
);
765+
}
766+
}
767+
768+
/**
769+
* Asserts that an entry was built by {@see self::pill()}.
770+
*
771+
* @param array<array-key, mixed> $entry Entry passed to {@see self::pills()}.
772+
*
773+
* @throws InvalidArgumentException if the entry does not carry the shape the factory produces.
774+
*
775+
* @phpstan-assert PillEntry $entry
776+
*/
777+
private static function assertPillEntry(array $entry): void
778+
{
779+
if (
780+
($entry['kind'] ?? null) !== 'pill'
781+
|| is_string($entry['label'] ?? null) === false
782+
|| is_string($entry['state'] ?? null) === false
783+
|| is_bool($entry['enabled'] ?? null) === false
784+
) {
785+
throw new InvalidArgumentException(
786+
PanelViewMessage::ENTRY_INVALID->getMessage('pill', 'pill'),
787+
);
788+
}
789+
}
790+
791+
/**
792+
* Asserts that an entry was built by {@see self::readout()}.
793+
*
794+
* @param array<array-key, mixed> $entry Entry passed to {@see self::readouts()}.
795+
*
796+
* @throws InvalidArgumentException if the entry does not carry the shape the factory produces.
797+
*
798+
* @phpstan-assert ReadoutEntry $entry
799+
*/
800+
private static function assertReadoutEntry(array $entry): void
801+
{
802+
if (
803+
($entry['kind'] ?? null) !== 'readout'
804+
|| is_string($entry['label'] ?? null) === false
805+
|| is_string($entry['value'] ?? null) === false
806+
|| is_string($entry['caption'] ?? null) === false
807+
) {
808+
throw new InvalidArgumentException(
809+
PanelViewMessage::ENTRY_INVALID->getMessage('readout', 'readout'),
810+
);
811+
}
812+
}
813+
684814
/**
685815
* Rejects column headings that are not plain strings.
686816
*

‎tests/CompositeBlockTest.php‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44

55
namespace PHPForge\Debug\Tests;
66

7+
use Closure;
8+
use InvalidArgumentException;
9+
use PHPForge\Debug\Exception\PanelViewMessage;
710
use PHPForge\Debug\PanelView;
8-
use PHPUnit\Framework\Attributes\Group;
11+
use PHPForge\Debug\Tests\Provider\MalformedEntryProvider;
12+
use PHPUnit\Framework\Attributes\{DataProviderExternal, Group};
913
use PHPUnit\Framework\TestCase;
1014

1115
/**
1216
* Unit tests for the {@see PanelView} composite blocks describing facts, readouts, pills, manifests, and sections.
17+
*
18+
* {@see MalformedEntryProvider} for test case data providers.
1319
*/
1420
#[Group('panel-view')]
1521
final class CompositeBlockTest extends TestCase
@@ -112,5 +118,18 @@ public function testSectionCountIsOptionalAndWrapsItsOwnBlocks(): void
112118
'A section keeps the tally it was given.',
113119
);
114120
}
121+
/**
122+
* @param Closure(): PanelView $build Composition that must reject the malformed entry.
123+
* @param string $kind Entry kind the rejected argument was meant to carry.
124+
*/
125+
#[DataProviderExternal(MalformedEntryProvider::class, 'entries')]
126+
public function testThrowInvalidArgumentExceptionForAnEntryTheFactoryDidNotBuild(Closure $build, string $kind): void
127+
{
128+
$this->expectException(InvalidArgumentException::class);
129+
$this->expectExceptionMessage(
130+
PanelViewMessage::ENTRY_INVALID->getMessage($kind, $kind),
131+
);
115132

133+
$build();
134+
}
116135
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPForge\Debug\Tests\Provider;
6+
7+
use Closure;
8+
use PHPForge\Debug\PanelView;
9+
use PHPForge\Debug\Tests\CompositeBlockTest;
10+
11+
/**
12+
* Provides composite-block entries no factory produced, whose rejection is verified by {@see CompositeBlockTest}.
13+
*/
14+
final class MalformedEntryProvider
15+
{
16+
/**
17+
* @return iterable<string, array{Closure(): PanelView, string}>
18+
*/
19+
public static function entries(): iterable
20+
{
21+
yield 'fact built by another factory' => [
22+
static fn(): PanelView => PanelView::create()->facts(
23+
['kind' => 'pill', 'label' => 'Charset', 'value' => 'UTF-8'],
24+
),
25+
'fact',
26+
];
27+
yield 'fact without a label' => [
28+
static fn(): PanelView => PanelView::create()->facts(
29+
['kind' => 'fact', 'label' => 8, 'value' => 'UTF-8'],
30+
),
31+
'fact',
32+
];
33+
yield 'fact without a value' => [
34+
static fn(): PanelView => PanelView::create()->facts(
35+
['kind' => 'fact', 'label' => 'Charset', 'value' => 8],
36+
),
37+
'fact',
38+
];
39+
yield 'empty fact' => [
40+
static fn(): PanelView => PanelView::create()->facts([]),
41+
'fact',
42+
];
43+
yield 'package built by another factory' => [
44+
static fn(): PanelView => PanelView::create()->manifest(
45+
'yiisoft/',
46+
['kind' => 'fact', 'name' => 'aliases', 'version' => 'v3.1.1'],
47+
),
48+
'package',
49+
];
50+
yield 'package without a name' => [
51+
static fn(): PanelView => PanelView::create()->manifest(
52+
'yiisoft/',
53+
['kind' => 'package', 'name' => 1, 'version' => 'v3.1.1'],
54+
),
55+
'package',
56+
];
57+
yield 'package without a version' => [
58+
static fn(): PanelView => PanelView::create()->manifest(
59+
'yiisoft/',
60+
['kind' => 'package', 'name' => 'aliases', 'version' => 3],
61+
),
62+
'package',
63+
];
64+
yield 'empty package' => [
65+
static fn(): PanelView => PanelView::create()->manifest('yiisoft/', []),
66+
'package',
67+
];
68+
yield 'pill built by another factory' => [
69+
static fn(): PanelView => PanelView::create()->pills(
70+
['kind' => 'fact', 'label' => 'APCu', 'state' => 'on', 'enabled' => true],
71+
),
72+
'pill',
73+
];
74+
yield 'pill without a label' => [
75+
static fn(): PanelView => PanelView::create()->pills(
76+
['kind' => 'pill', 'label' => 1, 'state' => 'on', 'enabled' => true],
77+
),
78+
'pill',
79+
];
80+
yield 'pill without a state' => [
81+
static fn(): PanelView => PanelView::create()->pills(
82+
['kind' => 'pill', 'label' => 'APCu', 'state' => 1, 'enabled' => true],
83+
),
84+
'pill',
85+
];
86+
yield 'pill without a boolean flag' => [
87+
static fn(): PanelView => PanelView::create()->pills(
88+
['kind' => 'pill', 'label' => 'APCu', 'state' => 'on', 'enabled' => 'yes'],
89+
),
90+
'pill',
91+
];
92+
yield 'readout built by another factory' => [
93+
static fn(): PanelView => PanelView::create()->readouts(
94+
['kind' => 'fact', 'label' => 'Yii', 'value' => '3', 'caption' => 'framework'],
95+
),
96+
'readout',
97+
];
98+
yield 'readout without a label' => [
99+
static fn(): PanelView => PanelView::create()->readouts(
100+
['kind' => 'readout', 'label' => 1, 'value' => '3', 'caption' => 'framework'],
101+
),
102+
'readout',
103+
];
104+
yield 'readout without a value' => [
105+
static fn(): PanelView => PanelView::create()->readouts(
106+
['kind' => 'readout', 'label' => 'Yii', 'value' => 3, 'caption' => 'framework'],
107+
),
108+
'readout',
109+
];
110+
yield 'readout without a caption' => [
111+
static fn(): PanelView => PanelView::create()->readouts(
112+
['kind' => 'readout', 'label' => 'Yii', 'value' => '3', 'caption' => 0],
113+
),
114+
'readout',
115+
];
116+
}
117+
}

0 commit comments

Comments
 (0)