summaryrefslogtreecommitdiffstats
path: root/tests/Inc/ModuleTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Inc/ModuleTest.php')
-rw-r--r--tests/Inc/ModuleTest.php188
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);
+ }
+}