2026-03-19 18:34:02 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MintyPHP\Tests\Architecture;
|
|
|
|
|
|
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
|
|
|
|
|
|
class FrontendRuntimeHostContractTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
use ProjectFileAssertionSupport;
|
|
|
|
|
|
|
|
|
|
public function testHostBoundComponentsExposeDataAppComponentMarkupHosts(): void
|
|
|
|
|
{
|
|
|
|
|
$defaultTemplate = $this->readProjectFile('templates/default.phtml');
|
|
|
|
|
$loginTemplate = $this->readProjectFile('templates/login.phtml');
|
|
|
|
|
$flashPartial = $this->readProjectFile('templates/partials/app-flash.phtml');
|
|
|
|
|
$topbarPartial = $this->readProjectFile('templates/partials/app-topbar.phtml');
|
|
|
|
|
$asideIconBar = $this->readProjectFile('templates/partials/app-main-aside-icon-bar.phtml');
|
|
|
|
|
$asidePartial = $this->readProjectFile('templates/partials/app-main-aside.phtml');
|
|
|
|
|
$bookmarkDialog = $this->readProjectFile('modules/bookmarks/templates/bookmark-dialogs.phtml');
|
|
|
|
|
$bookmarkPanelTemplate = $this->readProjectFile('modules/bookmarks/templates/aside-bookmarks-panel.phtml');
|
|
|
|
|
$sessionWarningDialog = $this->readProjectFile('templates/partials/app-session-warning-dialog.phtml');
|
|
|
|
|
|
|
|
|
|
$this->assertStringContainsString('id="page-config-app-components"', $defaultTemplate);
|
|
|
|
|
$this->assertStringContainsString('id="page-config-login-components"', $loginTemplate);
|
|
|
|
|
$this->assertStringNotContainsString("assetVersion('js/components/app-bookmark-init.js')", $defaultTemplate);
|
|
|
|
|
|
|
|
|
|
$this->assertStringContainsString('data-app-component="flash-auto-dismiss"', $flashPartial);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="tenant-switcher"', $topbarPartial);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="theme-controls"', $topbarPartial);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="contrast-toggle"', $topbarPartial);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="details-aside-toggle"', $topbarPartial);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="aside-panels"', $asideIconBar);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="sidebar-toggle"', $asidePartial);
|
2026-04-01 16:31:59 +02:00
|
|
|
$searchDialog = $this->readProjectFile('templates/partials/app-search-dialog.phtml');
|
|
|
|
|
$this->assertStringContainsString('data-app-component="global-search"', $searchDialog);
|
2026-03-19 18:34:02 +01:00
|
|
|
$this->assertStringContainsString('data-app-component="bookmark-panel"', $bookmarkPanelTemplate);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="bookmark-save"', $bookmarkDialog);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="session-warning"', $sessionWarningDialog);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testTabsHostsUseDataAppComponentContract(): void
|
|
|
|
|
{
|
|
|
|
|
$files = [
|
2026-04-22 15:49:10 +02:00
|
|
|
'templates/partials/app-user-profile.phtml',
|
2026-03-19 18:34:02 +01:00
|
|
|
'pages/admin/departments/_form.phtml',
|
|
|
|
|
'pages/admin/permissions/_form.phtml',
|
|
|
|
|
'pages/admin/roles/_form.phtml',
|
|
|
|
|
'pages/admin/settings/index(default).phtml',
|
|
|
|
|
'pages/admin/stats/index(default).phtml',
|
|
|
|
|
'pages/admin/tenants/_form.phtml',
|
|
|
|
|
'pages/admin/users/_form.phtml',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($files as $file) {
|
|
|
|
|
$content = $this->readProjectFile($file);
|
|
|
|
|
$this->assertStringContainsString('data-tabs', $content, $file);
|
|
|
|
|
$this->assertStringContainsString('data-app-component="tabs"', $content, $file);
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(ui): token-select primitive for large multi-selects
Adds tokenSelectForm() in core/Support/helpers/ui.php: an inline
typeahead combobox + flat alphabetical removable list, designed for
selections that outgrow the chip-header of the vendor MultiSelect
(roles, permissions, and similar admin pickers).
Contract:
- Hidden <select multiple name="<name>[]"> is the form submission source —
consumers read via $request->body() verbatim, no parsing
- Items are ['id' => int, <labelKey> => string, 'key' => string?]
where labelKeys default to ['description', 'label', 'name']; 'key' is
an invisible fuzzy-match hint
- $labelOverrides lets callers swap emptyState / removeTooltip /
noMatches / clearAll / countSuffix / errorMessage with domain copy
- $disabled renders pure-presentation list (no combobox, no remove)
Runtime:
- initTokenSelect in web/js/components/app-token-select.js is registered
as 'token-select' in app-init.js; destroy()/cleanupFns contract
- Syncs the hidden <select> on every mutation and dispatches 'change'
so dependent UI can react
Wired up: pages/admin/permissions/_form.phtml (assigned roles),
pages/admin/roles/_form.phtml (permissions + assignable roles).
Helper contract lives in tests/Support/Helpers/TokenSelectFormHelperTest.php;
runtime contract + entrypoint registration + host usage are enforced by
FrontendRuntime*ContractTest.php.
Coexists with multiSelectForm() — pick tokenSelectForm() when the
selected set can grow beyond ~10 items or typeahead is expected.
Docs in CLAUDE.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:52:32 +02:00
|
|
|
|
|
|
|
|
public function testRolesFormUsesTokenSelectHost(): void
|
|
|
|
|
{
|
|
|
|
|
$rolesForm = $this->readProjectFile('pages/admin/roles/_form.phtml');
|
|
|
|
|
$this->assertStringContainsString('tokenSelectForm(', $rolesForm);
|
|
|
|
|
}
|
2026-03-19 18:34:02 +01:00
|
|
|
}
|