findPatternMatchesInFiles('web/js', '/window\.alert\s*\(/', ['js']), $this->findPatternMatchesInFiles('modules/helpdesk/web/js', '/window\.alert\s*\(/', ['js']) ); $violations = array_values(array_unique($violations)); sort($violations); $this->assertSame([], $violations, "window.alert usage found in JS scope:\n" . implode("\n", $violations)); } public function testHelpdeskRuntimeComponentsDoNotAutoInitialize(): void { foreach ($this->helpdeskRuntimeComponentFiles() as $file) { $content = $this->readProjectFile($file); $this->assertStringNotContainsString('DOMContentLoaded', $content, $file); $this->assertStringNotContainsString("if (document.readyState === 'loading')", $content, $file); $this->assertStringNotContainsString('const container = document.querySelector', $content, $file); } } public function testHelpdeskRuntimeComponentsExposeDestroyLifecycle(): void { foreach ($this->helpdeskRuntimeComponentFiles() as $file) { $content = $this->readProjectFile($file); $this->assertStringContainsString('destroy:', $content, $file); } } public function testHelpdeskJsUsesCentralHttpLayer(): void { $violations = $this->findPatternMatchesInFiles('modules/helpdesk/web/js', '/\bfetch\s*\(/', ['js']); $this->assertSame([], $violations, "Direct fetch(...) found in helpdesk JS:\n" . implode("\n", $violations)); } public function testHelpdeskImportPolicyForCoreAndSiblingModules(): void { $root = $this->projectRootPath(); $base = $root . '/modules/helpdesk/web/js'; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($base, \FilesystemIterator::SKIP_DOTS)); $violations = []; /** @var \SplFileInfo $file */ foreach ($iterator as $file) { if (!$file->isFile() || $file->getExtension() !== 'js') { continue; } $content = file_get_contents($file->getPathname()); $this->assertNotFalse($content, 'Could not read file: ' . $file->getPathname()); $relativePath = str_replace($root . '/', '', $file->getPathname()); preg_match_all('/^\s*import\s+(?:[^\"\']+\s+from\s+)?[\"\']([^\"\']+)[\"\']/m', $content, $matches); foreach ($matches[1] as $importPathRaw) { $importPath = (string) $importPathRaw; if (str_starts_with($importPath, '/js/')) { continue; } if (str_starts_with($importPath, './') || str_starts_with($importPath, '../')) { if (preg_match('#(?:^|/)js/(core|components|pages)/#', $importPath) === 1) { $violations[] = $relativePath . ' => relative core import: ' . $importPath; } continue; } $violations[] = $relativePath . ' => invalid import path: ' . $importPath; } } $violations = array_values(array_unique($violations)); sort($violations); $this->assertSame([], $violations, "Helpdesk import policy violations:\n" . implode("\n", $violations)); } public function testLookupFieldComponentExportsRuntimeInitializerWithoutAutoInit(): void { $content = $this->readProjectFile('web/js/components/app-lookup-field.js'); $this->assertStringContainsString('export function initLookupFields', $content); $this->assertStringContainsString('destroy:', $content); $this->assertStringNotContainsString('DOMContentLoaded', $content); } /** * @return list */ private function helpdeskRuntimeComponentFiles(): array { return [ 'modules/helpdesk/web/js/helpdesk-detail.js', 'modules/helpdesk/web/js/helpdesk-domain-detail.js', 'modules/helpdesk/web/js/helpdesk-ticket.js', 'modules/helpdesk/web/js/helpdesk-team.js', 'modules/helpdesk/web/js/helpdesk-risk-radar.js', 'modules/helpdesk/web/js/helpdesk-settings.js', 'modules/helpdesk/web/js/handover-domain-select.js', 'modules/helpdesk/web/js/handover-schema-editor.js', ]; } }