refactor(addressbook): move from aside icon-bar to explorer nav link

Introduce generic explorer.nav_item slot type in app-main-aside.phtml
so modules can contribute links to the explorer nav panel.

Switch addressbook module from aside.tab_panel to explorer.nav_item.
Remove aside department-filter panel, AddressBookLayoutProvider, and
AddressBookSessionProvider (only served the now-removed aside panel).

Fix grid Name column overflow into Email column: CSS selector
.grid-name-cell > span:last-child never matched the <a> name link;
changed to :last-child and scoped flex-shrink:0 to :first-child only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 15:06:15 +01:00
parent 376cf67c31
commit 5b2ea6bf27
9 changed files with 35 additions and 408 deletions

View File

@@ -1,86 +0,0 @@
<?php
namespace MintyPHP\Tests\Unit\Module;
use MintyPHP\App\AppContainer;
use MintyPHP\Module\AddressBook\Providers\AddressBookLayoutProvider;
use MintyPHP\Tests\Support\AppContainerIsolationTrait;
use PHPUnit\Framework\TestCase;
/**
* Verifies that the AddressBookLayoutProvider correctly contributes
* the 'addressbook.nav' key to the layout context.
*/
final class LayoutContextProviderTest extends TestCase
{
use AppContainerIsolationTrait;
protected function setUp(): void
{
// Guard against other tests overwriting the global container
if (!($GLOBALS['minty_app_container'] ?? null) instanceof AppContainer
|| !($GLOBALS['minty_app_container'])->has(\MintyPHP\App\Module\ModuleRegistry::class)
|| !($GLOBALS['minty_app_container'])->has(\MintyPHP\Service\Access\UiAccessService::class)) {
$container = require dirname(__DIR__, 3) . '/lib/App/registerContainer.php';
$this->pushAppContainer($container);
setAppContainer($container);
}
}
protected function tearDown(): void
{
$this->restoreAppContainer();
}
public function testProviderReturnsAddressBookKey(): void
{
$provider = new AddressBookLayoutProvider();
$container = new AppContainer();
$session = [
'module.addressbook.departments_by_tenant' => [
[
'tenant' => ['uuid' => 'abc', 'description' => 'Tenant A'],
'departments' => [['id' => 1, 'description' => 'Dept 1']],
],
],
];
$result = $provider->provide($session, $container);
self::assertArrayHasKey('addressbook.nav', $result);
$ab = $result['addressbook.nav'];
self::assertArrayHasKey('url', $ab);
self::assertArrayHasKey('activeSearch', $ab);
self::assertArrayHasKey('activeTenants', $ab);
self::assertArrayHasKey('activeDepartments', $ab);
self::assertArrayHasKey('activeRoles', $ab);
self::assertArrayHasKey('activeCustomFilters', $ab);
self::assertArrayHasKey('peopleGroups', $ab);
self::assertCount(1, $ab['peopleGroups']);
}
public function testProviderReturnsEmptyPeopleGroupsWhenSessionEmpty(): void
{
$provider = new AddressBookLayoutProvider();
$container = new AppContainer();
$result = $provider->provide([], $container);
self::assertArrayHasKey('addressbook.nav', $result);
self::assertSame([], $result['addressbook.nav']['peopleGroups']);
}
public function testProviderIsIncludedInLayoutNavWhenModuleActive(): void
{
// When the module is active, appBuildLayoutNavContext() should include
// addressbook.nav via the provider loop
$layoutNav = appBuildLayoutNavContext([], [], []);
self::assertArrayHasKey(
'addressbook.nav',
$layoutNav,
'addressbook.nav key must be present in layoutNav when module is active'
);
}
}

View File

@@ -1,83 +0,0 @@
<?php
namespace MintyPHP\Tests\Unit\Module;
use MintyPHP\App\AppContainer;
use MintyPHP\Http\SessionStore;
use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Module\AddressBook\Providers\AddressBookSessionProvider;
use PHPUnit\Framework\TestCase;
/**
* @covers \MintyPHP\Module\AddressBook\Providers\AddressBookSessionProvider
*/
final class SessionProviderTest extends TestCase
{
protected function setUp(): void
{
$_SESSION = [];
}
protected function tearDown(): void
{
$_SESSION = [];
}
private function containerWithSessionStore(): AppContainer
{
$container = new AppContainer();
$container->set(SessionStoreInterface::class, static fn (): SessionStore => new SessionStore());
return $container;
}
public function testPopulateWithInvalidUserClearsSessionKey(): void
{
$_SESSION['module.addressbook.departments_by_tenant'] = [['tenant' => 'old']];
$provider = new AddressBookSessionProvider();
$provider->populate(['id' => 0], $this->containerWithSessionStore());
self::assertArrayNotHasKey('module.addressbook.departments_by_tenant', $_SESSION);
}
public function testPopulateWithMissingUserIdClearsSessionKey(): void
{
$_SESSION['module.addressbook.departments_by_tenant'] = [['tenant' => 'old']];
$provider = new AddressBookSessionProvider();
$provider->populate([], $this->containerWithSessionStore());
self::assertArrayNotHasKey('module.addressbook.departments_by_tenant', $_SESSION);
}
public function testPopulateReturnsEarlyWhenContainerMissesDependencies(): void
{
$container = $this->containerWithSessionStore();
$provider = new AddressBookSessionProvider();
// Container has SessionStore but no UserTenantContextService → provider returns early
$provider->populate(['id' => 42], $container);
self::assertArrayNotHasKey('module.addressbook.departments_by_tenant', $_SESSION);
}
public function testClearRemovesSessionKey(): void
{
$_SESSION['module.addressbook.departments_by_tenant'] = [
['tenant' => ['uuid' => 'abc'], 'departments' => []],
];
$provider = new AddressBookSessionProvider();
$provider->clear($this->containerWithSessionStore());
self::assertArrayNotHasKey('module.addressbook.departments_by_tenant', $_SESSION);
}
public function testClearIsIdempotent(): void
{
// Key doesn't exist — clear() should not throw
$provider = new AddressBookSessionProvider();
$provider->clear($this->containerWithSessionStore());
self::assertArrayNotHasKey('module.addressbook.departments_by_tenant', $_SESSION);
}
}