PageShellLandmarksTest.php 4.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. <?php
  2. /**
  3. * Every page shell opens <main> before the cover, so the cover and its <h1> sit inside the main landmark
  4. * and the skip link (#bx-content-main) lands on the page heading. The htmx partials swapped into it
  5. * (page_54/56/58) open no <main> of their own.
  6. */
  7. class PageShellLandmarksTest extends BxDolTestCase
  8. {
  9. private static function read(string $sPath): string
  10. {
  11. $s = file_get_contents(__DIR__ . '/../../../' . $sPath);
  12. if ($s === false)
  13. self::fail($sPath . ' is not readable.');
  14. return str_replace("\r\n", "\n", $s);
  15. }
  16. /**
  17. * Shell => [files it is made of, in output order; cover marker; marker of the page content; marker that must come right before </main>].
  18. */
  19. public static function shells(): array
  20. {
  21. $a = [
  22. 'default' => [['template/_sub_header.html', 'template/default.html', 'template/_sub_footer.html'], '<bx_include_auto:_page_cover.html />', '__page_main_code__', '</div> <!-- #bx-content-with-cover-wrapper -->'],
  23. 'application' => [['template/pt_application.html'], '__cover__', '__page_main_code__', '</div> <!-- #bx-content-wrapper -->'],
  24. 'artificer application' => [['modules/boonex/artificer/data/template/system/pt_application.html'], '__cover__', '__page_main_code__', '</div> <!-- #bx-content-wrapper -->'],
  25. ];
  26. foreach (['ocean', 'decorous', 'protean', 'lucid'] as $sTemplate) {
  27. $sDir = 'modules/boonex/' . $sTemplate . '/data/template/system/';
  28. $a[$sTemplate] = [[$sDir . '_sub_header.html', 'template/default.html', $sDir . '_sub_footer.html'], '__cover__', '__page_main_code__', '</div> <!-- #bx-content-wrapper -->'];
  29. $a[$sTemplate . ' application'] = [[$sDir . 'pt_application.html'], '__cover__', '__page_main_code__', '</div> <!-- #bx-content-wrapper -->'];
  30. }
  31. return $a;
  32. }
  33. #[\PHPUnit\Framework\Attributes\DataProvider('shells')]
  34. public function testCoverIsInsideMain(array $aFiles, string $sCover, string $sContent, string $sBeforeClose): void
  35. {
  36. $s = implode("\n", array_map([self::class, 'read'], $aFiles));
  37. $this->assertSame(1, preg_match_all('/<main\b/', $s), 'One <main> per page.');
  38. $this->assertSame(1, substr_count($s, '</main>'));
  39. $this->assertSame(1, substr_count($s, 'id="bx-content-main"'), 'The skip link target is the <main> itself.');
  40. $this->assertMatchesRegularExpression('/<main role="main" id="bx-content-main" tabindex="-1">/', $s);
  41. $iOpen = strpos($s, '<main');
  42. $iCover = strpos($s, $sCover);
  43. $iContent = strpos($s, $sContent);
  44. $iClose = strpos($s, '</main>');
  45. $this->assertNotFalse($iCover);
  46. $this->assertNotFalse($iContent);
  47. $this->assertTrue($iOpen < $iCover && $iCover < $iContent && $iContent < $iClose, '<main> opens before the cover and closes after the content.');
  48. $this->assertMatchesRegularExpression('/' . preg_quote($sBeforeClose, '/') . '\n\s*<\/main>/', $s, '<main> closes right after the content wrapper.');
  49. $this->assertDoesNotMatchRegularExpression('/<footer\b/', substr($s, $iOpen, $iClose - $iOpen), 'The footer stays outside <main>.');
  50. }
  51. public static function partials(): array
  52. {
  53. return array_map(fn($s) => [$s], ['template/_page_content_beg.html', 'template/_page_content_end.html', 'template/page_54.html', 'template/page_56.html', 'template/page_58.html']);
  54. }
  55. #[\PHPUnit\Framework\Attributes\DataProvider('partials')]
  56. public function testHtmxPartialsOpenNoMain(string $sPath): void
  57. {
  58. $s = self::read($sPath);
  59. $this->assertDoesNotMatchRegularExpression('/<\/?main\b/', $s, 'The partial is swapped inside the shell\'s <main>.');
  60. $this->assertStringNotContainsString('id="bx-content-main"', $s);
  61. }
  62. public function testMainKeepsTheFooterAtTheBottomInThemeShells(): void
  63. {
  64. $s = self::read('template/css/general.css');
  65. $this->assertMatchesRegularExpression('/#bx-content-wrapper \{[^}]*flex-grow: 3;/', $s);
  66. $this->assertMatchesRegularExpression('/#bx-content-main:has\(> #bx-content-wrapper\) \{[^}]*flex-grow: 3;/', $s, '<main> took #bx-content-wrapper\'s place as the flex item.');
  67. }
  68. }