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`