pluginsEnabled[] = 'luxtools'; parent::setUp(); // Setup config so that access to the TMP directory will be allowed // Use the built-in file.php endpoint. $conf ['plugin']['luxtools']['paths'] = TMP_DIR . '/filelistdata/' . "\n" . 'A> /Scape'; } public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); // copy test files to test directory \TestUtils::rcopy(TMP_DIR, dirname(__FILE__) . '/filelistdata'); } /** * Run a list of checks on the given document * * @param Document $doc * @param array $structure Array of selectors and expected count or content * @return void */ protected function structureCheck(Document $doc, $structure) { foreach ($structure as $selector => $expected) { if (is_numeric($expected)) { $this->assertEquals( $expected, $doc->find($selector)->count(), 'Selector ' . $selector . ' not found' ); } else { $this->assertStringContainsString( $expected, $doc->find($selector)->text(), 'Selector ' . $selector . ' not found' ); }; } } /** * This function checks that all files are listed in not recursive mode. * Uses {{files>...}} syntax for backwards compatibility (now handled by directory syntax). */ public function test_not_recursive() { global $conf; // Render filelist using files syntax (now handled by directory plugin) $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&direct=1}}'); $xhtml = p_render('xhtml', $instructions, $info); // We should find: // - example.txt // - exampleimage.png $result = strpos($xhtml, 'example.txt'); $this->assertFalse($result === false, '"example.txt" not listed'); $result = strpos($xhtml, 'exampleimage.png'); $this->assertFalse($result === false, '"exampleimage.png" not listed'); } /** * This function checks that all files are listed in recursive mode. * Uses {{files>...}} syntax for backwards compatibility (now handled by directory syntax). */ public function test_recursive() { // Render filelist using files syntax (now handled by directory plugin) $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&direct=1&recursive=1}}'); $xhtml = p_render('xhtml', $instructions, $info); // We should find: // - exampledir // - example2.txt // - example.txt // - exampleimage.png $result = strpos($xhtml, 'exampledir'); $this->assertFalse($result === false, '"exampledir" not listed'); $result = strpos($xhtml, 'example2.txt'); $this->assertFalse($result === false, '"example2.txt" not listed'); $result = strpos($xhtml, 'example.txt'); $this->assertFalse($result === false, '"example.txt" not listed'); $result = strpos($xhtml, 'exampleimage.png'); $this->assertFalse($result === false, '"exampleimage.png" not listed'); } /** * This function checks the rendering when style=list is explicitly specified. * Note: The files syntax is now handled by directory syntax and always renders as table. * This test is kept for backwards compatibility testing but expects table structure. */ public function testUnorderedList() { // Render filelist with explicit style=list (now ignored, renders as table) $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&style=list&direct=1&recursive=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); // Now renders as a table instead of list $structure = [ 'div.luxtools-plugin' => 1, 'div.luxtools-plugin table' => 1, ]; $this->structureCheck($doc, $structure); } /** * This function checks the rendering when style=olist is explicitly specified. * Note: The files syntax is now handled by directory syntax and always renders as table. * This test is kept for backwards compatibility testing but expects table structure. */ public function testOrderedList() { // Render filelist with explicit style=olist (now ignored, renders as table) $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&style=olist&direct=1&recursive=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); // Now renders as a table instead of ordered list $structure = [ 'div.luxtools-plugin' => 1, 'div.luxtools-plugin table' => 1, ]; $this->structureCheck($doc, $structure); } /** * This function checks that the table mode * generates the expected XHTML structure. */ public function test_table() { global $conf; // Render filelist $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&style=table&direct=1&recursive=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $structure = [ 'div.luxtools-plugin' => 1, 'div.luxtools-plugin table' => 1, 'div.luxtools-plugin table > tbody > tr' => 3, 'div.luxtools-plugin table > tbody > tr:nth-child(1) a' => 'example.txt', 'div.luxtools-plugin table > tbody > tr:nth-child(2) a' => 'exampledir/example2.txt', 'div.luxtools-plugin table > tbody > tr:nth-child(3) a' => 'exampleimage.png', ]; $this->structureCheck($doc, $structure); } public function test_default_maxheight_applies_scroll() { $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&style=list&direct=1&recursive=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $style = (string)$doc->find('div.luxtools-plugin')->attr('style'); $this->assertStringContainsString('max-height: 500px', $style); $this->assertStringContainsString('overflow-y: auto', $style); } public function test_maxheight_can_be_disabled() { $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&style=table&direct=1&recursive=1&maxheight=-1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $style = $doc->find('div.luxtools-plugin')->attr('style'); $this->assertTrue($style === null || $style === ''); } /** * This function checks that the images syntax renders a thumbnail gallery. */ public function test_images_gallery() { $instructions = p_get_instructions('{{images>' . TMP_DIR . '/filelistdata/*&direct=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $structure = [ 'div.luxtools-plugin.luxtools-gallery' => 1, 'div.luxtools-plugin.luxtools-gallery a' => 1, 'div.luxtools-plugin.luxtools-gallery img' => 1, ]; $this->structureCheck($doc, $structure); $this->assertStringContainsString('exampleimage.png', $xhtml); $this->assertStringContainsString('thumb=1', $xhtml); $this->assertStringContainsString('width="150"', $xhtml); $this->assertStringContainsString('height="150"', $xhtml); } /** * Grouping wrapper should use default flex mode with zero gap. */ public function test_grouping_default_flex() { $imagePath = TMP_DIR . '/filelistdata/exampleimage.png'; $syntax = '' . '{{image>' . $imagePath . '|One|120}}' . '{{image>' . $imagePath . '|Two|120}}' . ''; $instructions = p_get_instructions($syntax); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $structure = [ 'div.luxtools-grouping.luxtools-grouping--flex' => 1, 'div.luxtools-grouping .luxtools-imagebox' => 2, ]; $this->structureCheck($doc, $structure); $style = (string)$doc->find('div.luxtools-grouping')->first()->attr('style'); $this->assertStringContainsString('--luxtools-grouping-cols: 2', $style); $this->assertStringContainsString('--luxtools-grouping-gap: 0', $style); $this->assertStringContainsString('--luxtools-grouping-justify: start', $style); $this->assertStringContainsString('--luxtools-grouping-align: start', $style); } /** * Grouping wrapper should accept custom flex layout and gap. */ public function test_grouping_custom_flex() { $imagePath = TMP_DIR . '/filelistdata/exampleimage.png'; $syntax = '' . '{{image>' . $imagePath . '|One|120}}' . '{{image>' . $imagePath . '|Two|120}}' . ''; $instructions = p_get_instructions($syntax); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $structure = [ 'div.luxtools-grouping.luxtools-grouping--flex' => 1, 'div.luxtools-grouping .luxtools-imagebox' => 2, ]; $this->structureCheck($doc, $structure); $style = (string)$doc->find('div.luxtools-grouping')->first()->attr('style'); $this->assertStringContainsString('--luxtools-grouping-gap: 8px', $style); } /** * Grouping wrapper should accept justify and align controls. */ public function test_grouping_justify_and_align() { $imagePath = TMP_DIR . '/filelistdata/exampleimage.png'; $syntax = '' . '{{image>' . $imagePath . '|One|120}}' . '{{image>' . $imagePath . '|Two|120}}' . ''; $instructions = p_get_instructions($syntax); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $style = (string)$doc->find('div.luxtools-grouping')->first()->attr('style'); $this->assertStringContainsString('--luxtools-grouping-justify: space-between', $style); $this->assertStringContainsString('--luxtools-grouping-align: center', $style); } /** * Unknown grouping attributes should render a warning string. */ public function test_grouping_unknown_option_warning() { $imagePath = TMP_DIR . '/filelistdata/exampleimage.png'; $syntax = '' . '{{image>' . $imagePath . '|One|120}}' . ''; $instructions = p_get_instructions($syntax); $xhtml = p_render('xhtml', $instructions, $info); $this->assertStringContainsString('[grouping: unknown option(s): gpa]', $xhtml); } /** * Ensure the built-in file endpoint includes the host page id so file.php can * enforce per-page ACL. */ public function test_file_links_include_page_id_for_acl() { global $ID; $ID = 'luxtools:acltest'; $instructions = p_get_instructions('{{files>' . TMP_DIR . '/filelistdata/*&style=list&direct=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $href = (string)$doc->find('div.luxtools-plugin a')->first()->attr('href'); $this->assertNotSame('', $href); $this->assertStringContainsString('lib/plugins/luxtools/file.php', $href); $this->assertStringContainsString('id=luxtools%3Aacltest', $href); } /** * This function checks that the open syntax renders an inline link. */ public function test_open_link() { $instructions = p_get_instructions('{{open>/tmp/somewhere|Open here}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $structure = [ 'a.luxtools-open' => 1, ]; $this->structureCheck($doc, $structure); $this->assertStringContainsString('Open here', $xhtml); $this->assertStringContainsString('data-path="/tmp/somewhere"', $xhtml); } /** * This function checks that the directory syntax renders a flat table, * listing both folders and files. */ public function test_directory_table_flat() { $instructions = p_get_instructions('{{directory>' . TMP_DIR . '/filelistdata/&direct=1}}'); $xhtml = p_render('xhtml', $instructions, $info); $doc = new Document(); $doc->html($xhtml); $structure = [ 'div.luxtools-plugin' => 1, 'div.luxtools-plugin table' => 1, 'div.luxtools-plugin table > tbody > tr' => 3, 'a.luxtools-open' => 1, ]; $this->structureCheck($doc, $structure); // Should list the top-level entries, but not recurse into exampledir $this->assertStringContainsString('example.txt', $xhtml); $this->assertStringContainsString('exampleimage.png', $xhtml); $this->assertStringContainsString('exampledir', $xhtml); $this->assertStringNotContainsString('example2.txt', $xhtml); // Directory row should trigger the same behaviour as {{open>...}} for that folder $this->assertStringContainsString('data-path="/Scape/exampledir"', $xhtml); } /** * Strict ISO dates in plain text should be auto-linked to canonical day IDs. */ public function test_auto_link_iso_date_plain_text() { $instructions = p_get_instructions('Meeting with John on 2024-10-24.'); $xhtml = p_render('xhtml', $instructions, $info); if (strpos($xhtml, '>2024-10-24') === false) { throw new \Exception('Auto-link text for 2024-10-24 not found'); } if (strpos(urldecode($xhtml), 'chronological:2024:10:24') === false) { throw new \Exception('Auto-link target chronological:2024:10:24 not found'); } } /** * Auto-linking must not run inside code blocks. */ public function test_auto_link_skips_code_blocks() { $syntax = 'Outside date 2024-10-25.' . "\n\n" . 'Inside code 2024-10-24'; $instructions = p_get_instructions($syntax); $xhtml = p_render('xhtml', $instructions, $info); if (strpos($xhtml, '>2024-10-25') === false) { throw new \Exception('Outside date 2024-10-25 was not auto-linked'); } if (strpos(urldecode($xhtml), 'chronological:2024:10:25') === false) { throw new \Exception('Outside auto-link target chronological:2024:10:25 not found'); } if (strpos(urldecode($xhtml), 'chronological:2024:10:24') !== false) { throw new \Exception('Date inside code block was incorrectly auto-linked'); } if (strpos($xhtml, 'Inside code 2024-10-24') === false) { throw new \Exception('Code block content was unexpectedly altered'); } } /** * Calendar widget should render links to canonical day IDs. */ public function test_calendar_widget_links_canonical_day_ids() { $instructions = p_get_instructions('{{calendar>2024-10}}'); $xhtml = p_render('xhtml', $instructions, $info); if (strpos($xhtml, 'luxtools-calendar') === false) { throw new \Exception('Calendar container not rendered'); } $decoded = urldecode($xhtml); if (strpos($decoded, 'chronological:2024:10:01') === false) { throw new \Exception('Expected canonical day link for 2024-10-01 not found'); } if (strpos($decoded, 'chronological:2024:10:31') === false) { throw new \Exception('Expected canonical day link for 2024-10-31 not found'); } if (strpos($decoded, 'chronological:2024:10') === false) { throw new \Exception('Expected month link chronological:2024:10 not found in header'); } if (strpos($decoded, 'chronological:2024') === false) { throw new \Exception('Expected year link chronological:2024 not found in header'); } if (strpos($decoded, 'chronological:2024:09') === false) { throw new \Exception('Expected previous month canonical ID chronological:2024:09 not found'); } if (strpos($decoded, 'chronological:2024:11') === false) { throw new \Exception('Expected next month canonical ID chronological:2024:11 not found'); } if (strpos($xhtml, 'luxtools-calendar-nav') === false) { throw new \Exception('Calendar navigation container not rendered'); } if (strpos($xhtml, 'luxtools-calendar-nav-button') === false) { throw new \Exception('Calendar navigation buttons not rendered'); } if (strpos($xhtml, 'data-luxtools-calendar="1"') === false) { throw new \Exception('Calendar JS state attribute not rendered'); } if (strpos($xhtml, 'data-luxtools-ajax-url=') === false) { throw new \Exception('Calendar AJAX endpoint metadata not rendered'); } if (strpos($xhtml, 'luxtools-calendar-day') === false || strpos($xhtml, '