diff --git a/phpunit.xml b/phpunit.xml
index 337cec8..6907bc4 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -64,13 +64,13 @@
tests/Architecture/ListUiSharedPartialsContractTest.php
+ tests/Architecture/AddressBookModuleIsolationContractTest.php
+ tests/Architecture/BookmarksModuleIsolationContractTest.php
tests/Architecture/ModuleRegistryContractTest.php
tests/Architecture/ModuleBoundaryContractTest.php
tests/Architecture/ModuleManifestContractTest.php
tests/Architecture/ModuleProviderContractTest.php
tests/Architecture/ModuleUiContractTest.php
- tests/Architecture/NoAddressBookHardcodingTest.php
- tests/Architecture/NoBookmarksHardcodingTest.php
tests/Architecture/ApiSchemaContractTest.php
diff --git a/tests/Architecture/AddressBookModuleIsolationContractTest.php b/tests/Architecture/AddressBookModuleIsolationContractTest.php
new file mode 100644
index 0000000..8d47814
--- /dev/null
+++ b/tests/Architecture/AddressBookModuleIsolationContractTest.php
@@ -0,0 +1,76 @@
+grepDir($searchDir, '/address.book/i', ['*.php']);
+
+ self::assertSame([], $hits, sprintf(
+ "Search infrastructure still contains address-book hardcodings:\n%s",
+ implode("\n", $hits)
+ ));
+ }
+
+ public function testSearchConfigHasNoAddressBookHardcoding(): void
+ {
+ $file = dirname(__DIR__, 2) . '/lib/Support/SearchConfig.php';
+ $content = file_get_contents($file);
+ self::assertIsString($content);
+
+ self::assertStringNotContainsString('address-book', $content);
+ self::assertStringNotContainsString('address_book', $content);
+ self::assertStringNotContainsString('addressBook', $content);
+ }
+
+ public function testLayoutContextHasNoHardcodedAddressBookBlock(): void
+ {
+ $file = dirname(__DIR__, 2) . '/lib/Support/helpers/app.php';
+ $content = file_get_contents($file);
+ self::assertIsString($content);
+
+ self::assertDoesNotMatchRegularExpression(
+ "/'\s*addressBook\s*'\s*=>\s*\[/",
+ $content,
+ 'appBuildLayoutNavContext() still has hardcoded addressBook array block'
+ );
+ }
+
+ public function testAsideTemplatesHaveNoHardcodedPeopleUi(): void
+ {
+ $this->assertFilesDoNotContain(
+ [
+ dirname(__DIR__, 2) . '/templates/partials/app-main-aside-icon-bar.phtml',
+ dirname(__DIR__, 2) . '/templates/partials/app-main-aside.phtml',
+ ],
+ [
+ 'aside-tab-people',
+ '$canViewAddressBook',
+ '$addressBookUrl',
+ 'id="aside-panel-people"',
+ 'data-search-key="address-book"',
+ ]
+ );
+
+ $aside = file_get_contents(dirname(__DIR__, 2) . '/templates/partials/app-main-aside.phtml');
+ self::assertIsString($aside);
+ self::assertDoesNotMatchRegularExpression('/^\$canViewAddressBook\s*=/m', $aside);
+ self::assertDoesNotMatchRegularExpression('/^\$peopleGroups\s*=/m', $aside);
+ }
+
+ public function testCoreNoLongerContainsAddressBookServiceDirectory(): void
+ {
+ $coreServiceDir = dirname(__DIR__, 2) . '/lib/Service/AddressBook';
+ self::assertDirectoryDoesNotExist(
+ $coreServiceDir,
+ 'Addressbook service classes should live in modules/addressbook/lib, not in Core lib/Service'
+ );
+ }
+}
diff --git a/tests/Architecture/NoBookmarksHardcodingTest.php b/tests/Architecture/BookmarksModuleIsolationContractTest.php
similarity index 71%
rename from tests/Architecture/NoBookmarksHardcodingTest.php
rename to tests/Architecture/BookmarksModuleIsolationContractTest.php
index e514083..d4035f3 100644
--- a/tests/Architecture/NoBookmarksHardcodingTest.php
+++ b/tests/Architecture/BookmarksModuleIsolationContractTest.php
@@ -4,26 +4,27 @@ namespace MintyPHP\Tests\Architecture;
use PHPUnit\Framework\TestCase;
-final class NoBookmarksHardcodingTest extends TestCase
+final class BookmarksModuleIsolationContractTest extends TestCase
{
+ use ModuleExtractionContractSupport;
+
public function testCoreTemplatesHaveNoBookmarkSpecificMarkup(): void
{
- $files = [
- dirname(__DIR__, 2) . '/templates/default.phtml',
- dirname(__DIR__, 2) . '/templates/partials/app-topbar.phtml',
- dirname(__DIR__, 2) . '/templates/partials/app-main-aside.phtml',
- dirname(__DIR__, 2) . '/templates/partials/app-main-aside-icon-bar.phtml',
- ];
-
- foreach ($files as $file) {
- $content = file_get_contents($file);
- self::assertIsString($content);
- self::assertStringNotContainsString('aside-tab-bookmarks', $content, $file);
- self::assertStringNotContainsString('aside-panel-bookmarks', $content, $file);
- self::assertStringNotContainsString('data-app-bookmark-dialog', $content, $file);
- self::assertStringNotContainsString('bookmarks.nav', $content, $file);
- self::assertStringNotContainsString("components.bookmark", $content, $file);
- }
+ $this->assertFilesDoNotContain(
+ [
+ dirname(__DIR__, 2) . '/templates/default.phtml',
+ dirname(__DIR__, 2) . '/templates/partials/app-topbar.phtml',
+ dirname(__DIR__, 2) . '/templates/partials/app-main-aside.phtml',
+ dirname(__DIR__, 2) . '/templates/partials/app-main-aside-icon-bar.phtml',
+ ],
+ [
+ 'aside-tab-bookmarks',
+ 'aside-panel-bookmarks',
+ 'data-app-bookmark-dialog',
+ 'bookmarks.nav',
+ 'components.bookmark',
+ ]
+ );
}
public function testCoreBookmarkEntrypointsAreRemoved(): void
diff --git a/tests/Architecture/ModuleExtractionContractSupport.php b/tests/Architecture/ModuleExtractionContractSupport.php
new file mode 100644
index 0000000..6475fca
--- /dev/null
+++ b/tests/Architecture/ModuleExtractionContractSupport.php
@@ -0,0 +1,69 @@
+ $files
+ * @param list $forbiddenSnippets
+ */
+ private function assertFilesDoNotContain(array $files, array $forbiddenSnippets): void
+ {
+ foreach ($files as $file) {
+ $content = file_get_contents($file);
+ self::assertIsString($content);
+
+ foreach ($forbiddenSnippets as $snippet) {
+ self::assertStringNotContainsString($snippet, $content, $file);
+ }
+ }
+ }
+
+ /**
+ * @return list Lines matching the pattern
+ */
+ private function grepDir(string $dir, string $pattern, array $extensions): array
+ {
+ $hits = [];
+ if (!is_dir($dir)) {
+ return $hits;
+ }
+
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
+ RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($iterator as $file) {
+ $ext = $file->getExtension();
+ $matchesExt = false;
+ foreach ($extensions as $allowedExt) {
+ if ('*.' . $ext === $allowedExt) {
+ $matchesExt = true;
+ break;
+ }
+ }
+ if (!$matchesExt) {
+ continue;
+ }
+
+ $lines = file($file->getPathname(), FILE_IGNORE_NEW_LINES);
+ if ($lines === false) {
+ continue;
+ }
+ foreach ($lines as $lineNum => $line) {
+ if (preg_match($pattern, $line)) {
+ $relative = str_replace(dirname(__DIR__, 2) . '/', '', $file->getPathname());
+ $hits[] = sprintf('%s:%d: %s', $relative, $lineNum + 1, trim($line));
+ }
+ }
+ }
+
+ return $hits;
+ }
+}
diff --git a/tests/Architecture/NoAddressBookHardcodingTest.php b/tests/Architecture/NoAddressBookHardcodingTest.php
deleted file mode 100644
index c2289e2..0000000
--- a/tests/Architecture/NoAddressBookHardcodingTest.php
+++ /dev/null
@@ -1,171 +0,0 @@
-grepDir($searchDir, '/address.book/i', ['*.php']);
-
- self::assertSame([], $hits, sprintf(
- "Search infrastructure still contains address-book hardcodings:\n%s",
- implode("\n", $hits)
- ));
- }
-
- /**
- * SearchConfig facade must not contain hardcoded address-book references
- * (module provider bridge handles it dynamically).
- */
- public function testSearchConfigHasNoAddressBookHardcoding(): void
- {
- $file = dirname(__DIR__, 2) . '/lib/Support/SearchConfig.php';
- $content = file_get_contents($file);
- self::assertIsString($content);
-
- self::assertStringNotContainsString('address-book', $content);
- self::assertStringNotContainsString('address_book', $content);
- self::assertStringNotContainsString('addressBook', $content);
- }
-
- /**
- * Layout context builder (appBuildLayoutNavContext) must not contain
- * hardcoded addressBook data assembly.
- */
- public function testLayoutContextHasNoHardcodedAddressBookBlock(): void
- {
- $file = dirname(__DIR__, 2) . '/lib/Support/helpers/app.php';
- $content = file_get_contents($file);
- self::assertIsString($content);
-
- // The function should not contain 'addressBook' as an array key in the $layoutNav builder
- // (the module LayoutContextProvider provides it dynamically)
- self::assertDoesNotMatchRegularExpression(
- "/'\s*addressBook\s*'\s*=>\s*\[/",
- $content,
- 'appBuildLayoutNavContext() still has hardcoded addressBook array block'
- );
- }
-
- /**
- * Aside icon-bar template must not have hardcoded People tab.
- */
- public function testIconBarHasNoHardcodedPeopleTab(): void
- {
- $file = dirname(__DIR__, 2) . '/templates/partials/app-main-aside-icon-bar.phtml';
- $content = file_get_contents($file);
- self::assertIsString($content);
-
- self::assertStringNotContainsString('aside-tab-people', $content,
- 'Icon-bar still has hardcoded People tab (should come from module slot)');
- self::assertStringNotContainsString('$canViewAddressBook', $content,
- 'Icon-bar still references $canViewAddressBook variable');
- self::assertStringNotContainsString('$addressBookUrl', $content,
- 'Icon-bar still references $addressBookUrl variable');
- }
-
- /**
- * Aside panel template must not have hardcoded People panel/search entries.
- */
- public function testAsideHasNoHardcodedPeoplePanel(): void
- {
- $file = dirname(__DIR__, 2) . '/templates/partials/app-main-aside.phtml';
- $content = file_get_contents($file);
- self::assertIsString($content);
-
- // The hardcoded People panel used id="aside-panel-people" with data-aside-panel="people"
- self::assertStringNotContainsString('id="aside-panel-people"', $content,
- 'Aside still has hardcoded People panel element');
- // The hardcoded People panel extracted $canViewAddressBook as a standalone variable
- self::assertDoesNotMatchRegularExpression(
- '/^\$canViewAddressBook\s*=/m',
- $content,
- 'Aside still defines $canViewAddressBook as a standalone variable'
- );
- // The hardcoded People panel extracted $peopleGroups as a standalone variable
- self::assertDoesNotMatchRegularExpression(
- '/^\$peopleGroups\s*=/m',
- $content,
- 'Aside still defines $peopleGroups as a standalone variable'
- );
- self::assertStringNotContainsString(
- 'data-search-key="address-book"',
- $content,
- 'Aside still has static address-book search item markup'
- );
- }
-
- public function testCoreNoLongerContainsAddressBookServiceDirectory(): void
- {
- $coreServiceDir = dirname(__DIR__, 2) . '/lib/Service/AddressBook';
- self::assertDirectoryDoesNotExist(
- $coreServiceDir,
- 'Addressbook service classes should live in modules/addressbook/lib, not in Core lib/Service'
- );
- }
-
- /**
- * @return list Lines matching the pattern
- */
- private function grepDir(string $dir, string $pattern, array $extensions): array
- {
- $hits = [];
- if (!is_dir($dir)) {
- return $hits;
- }
-
- $iterator = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
- RecursiveIteratorIterator::LEAVES_ONLY
- );
-
- foreach ($iterator as $file) {
- $ext = $file->getExtension();
- $matchesExt = false;
- foreach ($extensions as $allowedExt) {
- if ('*.' . $ext === $allowedExt) {
- $matchesExt = true;
- break;
- }
- }
- if (!$matchesExt) {
- continue;
- }
-
- $lines = file($file->getPathname(), FILE_IGNORE_NEW_LINES);
- if ($lines === false) {
- continue;
- }
- foreach ($lines as $lineNum => $line) {
- if (preg_match($pattern, $line)) {
- $relative = str_replace(dirname(__DIR__, 2) . '/', '', $file->getPathname());
- $hits[] = sprintf('%s:%d: %s', $relative, $lineNum + 1, trim($line));
- }
- }
- }
-
- return $hits;
- }
-}
diff --git a/tests/README.md b/tests/README.md
index a2c0d1a..30ac456 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -35,5 +35,4 @@ ohne dass jede Aenderung immer die komplette Suite im Kopf behalten muss.
- `tests/Architecture/ModuleRegistryContractTest.php`
- `tests/Architecture/DetailValidationSummaryContractTest.php`
- `tests/Architecture/DetailActionPolicyContractTest.php`
-- `tests/Architecture/NoAddressBookHardcodingTest.php`
-- `tests/Architecture/NoBookmarksHardcodingTest.php`
+- `tests/Architecture/ContainerFactoryContractTest.php`