diff options
| author | Simon Rettberg | 2025-11-26 10:46:51 +0100 |
|---|---|---|
| committer | Simon Rettberg | 2025-12-12 15:16:59 +0100 |
| commit | 7c173411785f959d250d3dfbd7d4cfcb0e20f0e0 (patch) | |
| tree | 242157791a76afb7af23ec2cd3d22b599e54ce9d /tests/Inc/ModuleTest.php | |
| parent | [exams] Fix incorrect count() clause (diff) | |
| download | slx-admin-7c173411785f959d250d3dfbd7d4cfcb0e20f0e0.tar.gz slx-admin-7c173411785f959d250d3dfbd7d4cfcb0e20f0e0.tar.xz slx-admin-7c173411785f959d250d3dfbd7d4cfcb0e20f0e0.zip | |
Add tests using PHPUnit
Tests generated by Junie AI. Might not have the best possible quality
but at least we got something, and if it turns out to be complete
rubbish, we can just throw it out again without any issues, as this is
independent of the actual code base.
Diffstat (limited to 'tests/Inc/ModuleTest.php')
| -rw-r--r-- | tests/Inc/ModuleTest.php | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/tests/Inc/ModuleTest.php b/tests/Inc/ModuleTest.php new file mode 100644 index 00000000..05791022 --- /dev/null +++ b/tests/Inc/ModuleTest.php @@ -0,0 +1,188 @@ +<?php + +use PHPUnit\Framework\TestCase; + +/** + * Tests for the global Module loader include (inc/module.inc.php). + * + * These tests exercise dependency resolution, activation/autoloading, page loading, + * and the various query helpers (getEnabled/getAll/getActivated/getDependencies,...). + * + * We create temporary test modules under ./modules with minimal config.json, + * page.inc.php, and inc/*.inc.php files, then clean them up in tearDown(). + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class ModuleTest extends TestCase +{ + private string $baseDir; + private array $created = []; + + protected function setUp(): void + { + ErrorHandler::reset(); + $this->baseDir = getcwd() . '/modules'; + // Ensure a clean environment: the Module registry initializes lazily in Module::init(), + // and we haven't referenced Module yet in this process. + $this->makeModule('TestDep', [ + 'dependencies' => [], + 'client-plugin' => false, + 'category' => 'utilities', + ]); + $this->writeFile('modules/TestDep/page.inc.php', + "<?php\nclass Page_TestDep extends Page { protected function doPreprocess(){} protected function doRender(){} }\n"); + // A helper class to be autoloaded only when TestMain is activated + $this->makeModule('TestMain', [ + 'dependencies' => ['testdep'], // must reference the lowercase id key + 'client-plugin' => true, + 'category' => 'main', + 'css' => [], + 'scripts' => [], + 'collapse' => true, + ]); + $this->writeFile('modules/TestMain/page.inc.php', + "<?php\nclass Page_TestMain extends Page { protected function doPreprocess(){} protected function doRender(){} }\n"); + $this->writeFile('modules/TestMain/inc/abchelper.inc.php', + "<?php\nclass AbcHelper { public static function id(){ return 123; } }\n"); + // Provide optional assets that getCss/getScripts may include when activated + $this->writeFile('modules/TestMain/style.css', '/* css */'); + $this->writeFile('modules/TestMain/clientscript.js', '// js'); + + // A module with a missing dependency to test failure path + $this->makeModule('BadMain', [ 'dependencies' => ['no_such_dep'] ]); + $this->writeFile('modules/BadMain/page.inc.php', + "<?php\nclass Page_BadMain extends Page { protected function doPreprocess(){} protected function doRender(){} }\n"); + } + + protected function tearDown(): void + { + // Remove created test modules + // Iterate in reverse to delete children before parents + foreach (array_reverse($this->created) as $path) { + if (is_file($path)) { + @unlink($path); + } elseif (is_dir($path)) { + @rmdir($path); + } + } + $this->created = []; + } + + private function makeModule(string $name, array $config): void + { + $dir = $this->baseDir . '/' . $name; + $inc = $dir . '/inc'; + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + $this->created[] = $dir; + } + if (!is_dir($inc)) { + mkdir($inc, 0777, true); + $this->created[] = $inc; + } + $this->writeFile($dir . '/config.json', json_encode($config)); + } + + private function writeFile(string $path, string $content): void + { + file_put_contents($path, $content); + $this->created[] = $path; + } + + public function testInitAndGetAndActivationAndNewPage(): void + { + // Load module list from filesystem + require_once 'inc/module.inc.php'; + Module::init(); + + // Resolve/activate and check availability + $this->assertTrue(Module::isAvailable('testmain', true)); + $main = Module::get('testmain'); + $this->assertNotFalse($main); + $this->assertSame('TestMain', $main->getIdentifier()); + // getDisplayName falls back to !!name!! without dictionary entry + $this->assertSame('!!TestMain!!', $main->getDisplayName()); + // Category helpers + $this->assertSame('main', $main->getCategory()); + $this->assertSame('Cat:main', $main->getCategoryName()); + $this->assertTrue($main->doCollapse()); + $this->assertSame('modules/TestMain', $main->getDir()); + + // Test autoloader from module/inc + $this->assertTrue(class_exists('AbcHelper'), 'Autoloader should expose AbcHelper from module/inc'); + $this->assertSame(123, AbcHelper::id()); + + // Page loader + $page = $main->newPage(); + $this->assertInstanceOf(Page::class, $page); + $this->assertInstanceOf(Page_TestMain::class, $page); + + // Assets only reported when directly activated and client-plugin true + $css = $main->getCss(); + $js = $main->getScripts(); + $this->assertContains('style.css', $css); + $this->assertContains('clientscript.js', $js); + } + + public function testDependencyResolutionAndQueries(): void + { + require_once 'inc/module.inc.php'; + Module::init(); + + // BadMain is not available due to missing dep + // The Module loader triggers a user-level warning in this case; expect it so the test doesn't fail + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + if ($errstr === 'Disabling module BadMain: Dependency no_such_dep failed.') + return; + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + }); + $this->assertFalse(Module::isAvailable('badmain')); + $this->assertFalse(Module::get('badmain')); + + // TestDep alone is available + $this->assertTrue(Module::isAvailable('testdep')); + $dep = Module::get('testdep'); + $this->assertNotFalse($dep); + + // Enabled list contains both TestDep and TestMain when we activate main + Module::isAvailable('testmain', true); + $enabled = Module::getEnabled(true); + $ids = array_map(function($m){ return $m->getIdentifier(); }, $enabled); + $this->assertContains('TestDep', $ids); + $this->assertContains('TestMain', $ids); + + // All includes even those with missing deps (after resolution attempt) + $all = Module::getAll(); + $allIds = array_map(function($m){ return $m->getIdentifier(); }, $all); + $this->assertContains('TestDep', $allIds); + $this->assertContains('TestMain', $allIds); + $this->assertContains('BadMain', $allIds); // present but unusable + + // Activated contains modules with an activated depth marker + $activated = Module::getActivated(); + $this->assertNotEmpty($activated); + $actVals = array_values($activated); + $this->assertContainsOnlyInstancesOf(Module::class, $actVals); + } + + public function testTransitiveDependenciesListed(): void + { + // Create Dep2 and make TestDep depend on it; then init fresh process registry + $this->makeModule('Dep2', [ 'dependencies' => [] ]); + $this->writeFile('modules/Dep2/page.inc.php', + "<?php\nclass Page_Dep2 extends Page { protected function doPreprocess(){} protected function doRender(){} }\n"); + // Overwrite TestDep config to depend on dep2 + $this->writeFile('modules/TestDep/config.json', json_encode(['dependencies' => ['dep2']])); + + require_once 'inc/module.inc.php'; + Module::init(); + // Activate main (which depends on testdep which depends on dep2) + Module::isAvailable('testmain', true); + $main = Module::get('testmain'); + $list = $main->getDependencies(); + // Should include transitive dependency + $this->assertContains('testdep', $list); + $this->assertContains('dep2', $list); + } +} |
