originalDataDir = $conf['datadir']; } public function tearDown(): void { global $conf; $conf['datadir'] = $this->originalDataDir; parent::tearDown(); } public function treeConfigProvider() { return [ 'Default configuration' => [ 'namespace' => 'namespace', 'depth' => -1, 'flags' => 0, 'expected' => [ '+namespace:start', '+namespace:page1', '+namespace:page2', '+namespace:subns', '++namespace:subns:start', '++namespace:subns:page3', '++namespace:subns:deeper', '+++namespace:subns:deeper:start', '+++namespace:subns:deeper:page4' ] ], 'Depth limit 1' => [ 'namespace' => 'namespace', 'depth' => 1, 'flags' => 0, 'expected' => [ '+namespace:start', '+namespace:page1', '+namespace:page2', '+namespace:subns', '++namespace:subns:start', '++namespace:subns:page3', '++namespace:subns:deeper' ] ], 'Depth limit 1 with NS_AS_STARTPAGE' => [ 'namespace' => 'namespace', 'depth' => 1, 'flags' => PageTreeBuilder::FLAG_NS_AS_STARTPAGE, 'expected' => [ '+namespace:page1', '+namespace:page2', '+namespace:subns:start', '++namespace:subns:page3', '++namespace:subns:deeper:start' ] ], 'FLAG_NO_NS' => [ 'namespace' => 'namespace', 'depth' => -1, 'flags' => PageTreeBuilder::FLAG_NO_NS, 'expected' => [ '+namespace:start', '+namespace:page1', '+namespace:page2' ] ], 'FLAG_NO_PAGES' => [ 'namespace' => 'namespace', 'depth' => -1, 'flags' => PageTreeBuilder::FLAG_NO_PAGES, 'expected' => [ '+namespace:subns', '++namespace:subns:deeper' ] ], 'FLAG_NS_AS_STARTPAGE' => [ 'namespace' => 'namespace', 'depth' => -1, 'flags' => PageTreeBuilder::FLAG_NS_AS_STARTPAGE, 'expected' => [ '+namespace:page1', '+namespace:page2', '+namespace:subns:start', '++namespace:subns:page3', '++namespace:subns:deeper:start', '+++namespace:subns:deeper:page4' ] ], 'Combined FLAG_NO_NS and FLAG_NS_AS_STARTPAGE' => [ 'namespace' => 'namespace', 'depth' => -1, 'flags' => PageTreeBuilder::FLAG_NO_NS | PageTreeBuilder::FLAG_NS_AS_STARTPAGE, 'expected' => [ '+namespace:page1', '+namespace:page2' ] ], 'FLAG_SELF_TOP' => [ 'namespace' => 'namespace', 'depth' => -1, 'flags' => PageTreeBuilder::FLAG_SELF_TOP, 'expected' => [ '+namespace', '++namespace:start', '++namespace:page1', '++namespace:page2', '++namespace:subns', '+++namespace:subns:start', '+++namespace:subns:page3', '+++namespace:subns:deeper', '++++namespace:subns:deeper:start', '++++namespace:subns:deeper:page4' ] ], ]; } /** * @dataProvider treeConfigProvider */ public function testPageTreeConfigurations(string $namespace, int $depth, int $flags, array $expected) { $tree = new PageTreeBuilder($namespace, $depth); if ($flags) { $tree->addFlag($flags); } $tree->generate(); $result = explode("\n", (string)$tree); sort($expected); sort($result); $this->assertEquals($expected, $result); } /** * This is the same test as above, but pretending that our data directory is in our test namespace. * * @dataProvider treeConfigProvider */ public function testTopLevelTree(string $namespace, int $depth, int $flags, array $expected) { global $conf; $conf['datadir'] .= '/namespace'; $expected = array_map(function ($item) use ($namespace) { return preg_replace('/namespace:?/', '', $item); }, $expected); $namespace = ''; $this->testPageTreeConfigurations($namespace, $depth, $flags, $expected); } public function testPageTreeLeaves() { $tree = new PageTreeBuilder('namespace'); $tree->generate(); $leaves = $tree->getLeaves(); $branches = $tree->getBranches(); // Test that we have both leaves and branches $this->assertGreaterThan(0, count($leaves), 'Should have leaf pages'); $this->assertGreaterThan(0, count($branches), 'Should have branch pages'); // The total should equal all pages $this->assertEquals(count($tree->getAll()), count($leaves) + count($branches), 'Leaves + branches should equal total pages'); } public function invalidIdFilteringProvider() { return [ 'invalid IDs filtered by default' => [ 'flags' => 0, 'expectFiltered' => true, ], 'invalid IDs kept with FLAG_KEEP_INVALID' => [ 'flags' => PageTreeBuilder::FLAG_KEEP_INVALID, 'expectFiltered' => false, ], ]; } /** * @dataProvider invalidIdFilteringProvider */ public function testInvalidIdFiltering(int $flags, bool $expectFiltered) { global $conf; $nsDir = $conf['datadir'] . '/namespace'; // Create files with invalid IDs directly in the filesystem $invalidFiles = [ '__template.txt', // namespace template '_template.txt', // leading underscore ]; foreach ($invalidFiles as $file) { io_saveFile($nsDir . '/' . $file, 'This should not appear in the tree'); } // Create directories with invalid names $invalidDirs = [ '_private', // leading underscore ]; foreach ($invalidDirs as $dir) { $dirPath = $nsDir . '/' . $dir; io_mkdir_p($dirPath); io_saveFile($dirPath . '/start.txt', 'This should not appear in the tree'); } $tree = new PageTreeBuilder('namespace'); if ($flags) { $tree->addFlag($flags); } $tree->generate(); $allIds = array_map(fn($node) => $node->getId(), $tree->getAll()); // Check invalid pages foreach ($invalidFiles as $file) { $invalidId = pathID('namespace/' . $file); if ($expectFiltered) { $this->assertNotContains($invalidId, $allIds, "Invalid page '$file' should be filtered"); } else { $this->assertContains($invalidId, $allIds, "Invalid page '$file' should be kept"); } } // Check invalid namespaces foreach ($invalidDirs as $dir) { $invalidId = pathID('namespace/' . $dir); if ($expectFiltered) { $this->assertNotContains($invalidId, $allIds, "Invalid namespace '$dir' should be filtered"); } else { $this->assertContains($invalidId, $allIds, "Invalid namespace '$dir' should be kept"); } } // Valid pages and namespaces should always be present $this->assertContains('namespace:page1', $allIds); $this->assertContains('namespace:page2', $allIds); $this->assertContains('namespace:subns', $allIds); } }