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. * * Note: The search panel legitimately contains data-search-key="address-book" * for the global search results — that is NOT a hardcoded People panel. */ 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' ); } /** * @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; } }