From 2ce717367dc749a2c1c1a7a00d3be136908c66da Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Wed, 19 Aug 2026 10:30:44 -0400 Subject: [PATCH 1/2] feat(ui): share User guest and RBAC section rendering and support selecting filtered tabs. --- CHANGELOG.md | 1 + src/Helper/Tabs.php | 6 ++- src/Panel/User/UserGuestRenderer.php | 32 +++++++++++++++ src/Panel/User/UserRbacRenderer.php | 41 +++++++++++++++++++ tests/Helper/TabsTest.php | 31 ++++++++++++++ tests/Panel/User/UserGuestRendererTest.php | 36 +++++++++++++++++ tests/Panel/User/UserRbacRendererTest.php | 47 ++++++++++++++++++++++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/Panel/User/UserGuestRenderer.php create mode 100644 src/Panel/User/UserRbacRenderer.php create mode 100644 tests/Panel/User/UserGuestRendererTest.php create mode 100644 tests/Panel/User/UserRbacRendererTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 2575aa8..088933e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,3 +20,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat(ui): share database EXPLAIN markup across debugger adapters. - feat(ui): add shared profiler normalization and Timeline rendering contracts. - feat(tests): add unit tests for panel snapshots and enhance existing test coverage. +- feat(ui): share User guest and RBAC section rendering and support selecting filtered tabs. diff --git a/src/Helper/Tabs.php b/src/Helper/Tabs.php index b239831..e6e43bf 100644 --- a/src/Helper/Tabs.php +++ b/src/Helper/Tabs.php @@ -17,14 +17,16 @@ final class Tabs * @param non-empty-string $id * @param non-empty-string $ariaLabel * @param list $tabs + * @param int $activeIndex Zero-based index of the tab selected on initial render. */ - public static function render(string $id, string $ariaLabel, array $tabs): string + public static function render(string $id, string $ariaLabel, array $tabs, int $activeIndex = 0): string { $items = []; $panels = []; foreach ($tabs as $index => $tab) { - $active = $index === 0; + $active = $index === $activeIndex; + $tabId = "{$id}-tab-{$index}"; $panelId = "{$id}-panel-{$index}"; diff --git a/src/Panel/User/UserGuestRenderer.php b/src/Panel/User/UserGuestRenderer.php new file mode 100644 index 0000000..eb5875d --- /dev/null +++ b/src/Panel/User/UserGuestRenderer.php @@ -0,0 +1,32 @@ +content( + 'The request was served to a guest, so there are no identity attributes, roles, or permissions to inspect.', + ), + P::tag() + ->content( + 'Sign in and reload to inspect the identity. User switching remains unavailable to guests.', + ), + ); + } +} diff --git a/src/Panel/User/UserRbacRenderer.php b/src/Panel/User/UserRbacRenderer.php new file mode 100644 index 0000000..81430dc --- /dev/null +++ b/src/Panel/User/UserRbacRenderer.php @@ -0,0 +1,41 @@ +content('Roles') + ->render() . $rolesGrid; + } + + if ($permissionsGrid !== null) { + $html .= H2::tag() + ->content('Permissions') + ->render() . $permissionsGrid; + } + + return $html; + } +} diff --git a/tests/Helper/TabsTest.php b/tests/Helper/TabsTest.php index 2b5b93d..56a6085 100644 --- a/tests/Helper/TabsTest.php +++ b/tests/Helper/TabsTest.php @@ -52,4 +52,35 @@ public function testRenderMarksOnlyTheFirstTabAndPanelAsActive(): void 'Inactive panel must remain hidden until its tab is selected.', ); } + + public function testRenderSelectsTheRequestedInitialTab(): void + { + self::assertSame( + << + +
+
+

Two

+
+
+ HTML, + Tabs::render( + 'example', + 'Example tabs', + [ + ['label' => 'First', 'content' => '

One

'], + ['label' => 'Second', 'content' => '

Two

'], + ], + 1, + ), + 'The requested tab and panel must be the only initially active pair.', + ); + } } diff --git a/tests/Panel/User/UserGuestRendererTest.php b/tests/Panel/User/UserGuestRendererTest.php new file mode 100644 index 0000000..e96f885 --- /dev/null +++ b/tests/Panel/User/UserGuestRendererTest.php @@ -0,0 +1,36 @@ + +

+ No user authenticated in this request +

+ The request was served to a guest, so there are no identity attributes, roles, or permissions to inspect. +

+ Sign in and reload to inspect the identity. User switching remains unavailable to guests. +

+ + HTML, + UserGuestRenderer::render(), + 'Guest rendering must remain identical across framework adapters.', + ); + } +} diff --git a/tests/Panel/User/UserRbacRendererTest.php b/tests/Panel/User/UserRbacRendererTest.php new file mode 100644 index 0000000..5500c7b --- /dev/null +++ b/tests/Panel/User/UserRbacRendererTest.php @@ -0,0 +1,47 @@ + + Permissions + + HTML, + UserRbacRenderer::render(null, ''), + 'A null category must be omitted while a captured empty category keeps its heading.', + ); + } + public function testRenderReturnsBothSectionsInCanonicalOrder(): void + { + self::assertSame( + << + Roles +

+ Permissions +

+ HTML, + UserRbacRenderer::render( + '
', + '
', + ), + 'Roles must precede Permissions with the canonical headings.', + ); + } +} From 5d186ef8f1ca4deddfe30d9396e953a9ddb68f57 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Wed, 19 Aug 2026 10:38:50 -0400 Subject: [PATCH 2/2] Apply fixed coderabbitai review. --- src/Helper/Tabs.php | 9 +++++++++ tests/Helper/TabsTest.php | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/Helper/Tabs.php b/src/Helper/Tabs.php index e6e43bf..508f17f 100644 --- a/src/Helper/Tabs.php +++ b/src/Helper/Tabs.php @@ -4,10 +4,13 @@ namespace PHPForge\Debug\Helper; +use InvalidArgumentException; use UIAwesome\Html\Flow\Div; use UIAwesome\Html\List\{Li, Ul}; use UIAwesome\Html\Palpable\A; +use function array_key_exists; + /** * Renders the shared accessible tab pattern used by debug panels. */ @@ -21,6 +24,12 @@ final class Tabs */ public static function render(string $id, string $ariaLabel, array $tabs, int $activeIndex = 0): string { + if ($tabs !== [] && !array_key_exists($activeIndex, $tabs)) { + throw new InvalidArgumentException( + 'The active tab index must identify a supplied tab.', + ); + } + $items = []; $panels = []; diff --git a/tests/Helper/TabsTest.php b/tests/Helper/TabsTest.php index 56a6085..7fb96f1 100644 --- a/tests/Helper/TabsTest.php +++ b/tests/Helper/TabsTest.php @@ -4,6 +4,7 @@ namespace PHPForge\Debug\Tests\Helper; +use InvalidArgumentException; use PHPForge\Debug\Helper\Tabs; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; @@ -16,6 +17,19 @@ #[Group('helpers')] final class TabsTest extends TestCase { + public function testRenderAllowsAnyActiveIndexForAnEmptyTabList(): void + { + self::assertSame( + << +
+
+ HTML, + Tabs::render('empty', 'Empty tabs', [], -1), + 'An empty tab list must preserve its existing rendering for any requested index.', + ); + } + public function testRenderMarksOnlyTheFirstTabAndPanelAsActive(): void { $html = Tabs::render( @@ -83,4 +97,30 @@ public function testRenderSelectsTheRequestedInitialTab(): void 'The requested tab and panel must be the only initially active pair.', ); } + + public function testRenderThrowsForANegativeActiveIndex(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The active tab index must identify a supplied tab.'); + + Tabs::render( + 'example', + 'Example tabs', + [['label' => 'First', 'content' => '

One

']], + -1, + ); + } + + public function testRenderThrowsForAnOutOfRangeActiveIndex(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The active tab index must identify a supplied tab.'); + + Tabs::render( + 'example', + 'Example tabs', + [['label' => 'First', 'content' => '

One

']], + 1, + ); + } }