diff --git a/phpunit.xml b/phpunit.xml index 1b7cef5f..0558506c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -27,5 +27,8 @@ + + + diff --git a/site-dynamic-php/index.php b/site-dynamic-php/index.php index 5bf35bf0..fbe2b1b9 100644 --- a/site-dynamic-php/index.php +++ b/site-dynamic-php/index.php @@ -14,7 +14,10 @@ JoshBruce\Site\SiteDynamic\Emitter::emit( response:JoshBruce\Site\HttpResponse::from( - request: JoshBruce\Site\HttpRequest::fromGlobals() + request: JoshBruce\Site\HttpRequest::with( + JoshBruce\Site\ServerGlobals::init(), + JoshBruce\Site\FileSystem::init() + ) ) ); exit; diff --git a/src/Content/Markdown.php b/src/Content/Markdown.php index 49429a04..5213a6e3 100644 --- a/src/Content/Markdown.php +++ b/src/Content/Markdown.php @@ -7,6 +7,7 @@ use Eightfold\Markdown\Markdown as MarkdownConverter; use JoshBruce\Site\File; +use JoshBruce\Site\FileSystemInterface; use JoshBruce\Site\PageComponents\Data; use JoshBruce\Site\PageComponents\DateBlock; @@ -24,9 +25,9 @@ class Markdown private string $body = ''; - public static function for(File $file): Markdown + public static function for(File $file, FileSystemInterface $in): Markdown { - return new Markdown($file); + return new Markdown($file, $in); } public static function markdownConverter(): MarkdownConverter @@ -49,8 +50,10 @@ public static function markdownConverter(): MarkdownConverter ); } - private function __construct(private File $file) - { + private function __construct( + private File $file, + private FileSystemInterface $fileSystem + ) { } public function html(): string @@ -77,10 +80,13 @@ public function html(): string $b = ''; $template = $templateMap[$templateKey]; if ($templateKey === 'loglist') { - $b = $template::create($this->file); + $b = $template::create($this->file(), $this->fileSystem()); } else { - $b = $template::create($this->frontMatter()); + $b = $template::create( + $this->frontMatter(), + $this->fileSystem() + ); } @@ -118,11 +124,11 @@ public function pageTitle(): string $titles = []; $titles[] = $this->frontMatter()->title(); - $file = clone $this->file; + $file = clone $this->file(); while ($file->canGoUp()) { $file = $file->up(); - $m = Markdown::for($file); + $m = Markdown::for($file, $this->fileSystem()); $titles[] = $m->frontMatter()->title(); } @@ -131,16 +137,21 @@ public function pageTitle(): string return implode(' | ', $titles); } - public function canonicalURl(): string + public function file(): File { - return $this->file->canonicalUrl(); + return $this->file; } private function fileContent(): string { - if (strlen($this->fileContent) === 0 and $this->file->found()) { - $this->fileContent = $this->file->contents(); + if (strlen($this->fileContent) === 0 and $this->file()->found()) { + $this->fileContent = $this->file()->contents(); } return $this->fileContent; } + + private function fileSystem(): FileSystemInterface + { + return $this->fileSystem; + } } diff --git a/src/Documents/Sitemap.php b/src/Documents/Sitemap.php index 2cc565da..f46529d5 100644 --- a/src/Documents/Sitemap.php +++ b/src/Documents/Sitemap.php @@ -7,30 +7,31 @@ use Eightfold\XMLBuilder\Document; use Eightfold\XMLBuilder\Element; -use JoshBruce\Site\FileSystem; +use JoshBruce\Site\FileSystemInterface; use JoshBruce\Site\File; use JoshBruce\Site\Content\Markdown; class Sitemap { - public static function create(): string + public static function create(FileSystemInterface $fileSystem): string { - $finder = FileSystem::finder()->name('content.md')->sortByName() + $finder = $fileSystem->publishedContentFinder()->sortByName() ->notContains('redirect:') ->notContains('noindex:'); $markdown = []; foreach ($finder as $file) { $markdown[] = Markdown::for( - File::at($file->getPathname()) + File::at($file->getPathname(), $fileSystem), + $fileSystem ); } $urls = []; foreach ($markdown as $m) { $urls[] = Element::url( - Element::loc($m->canonicalUrl()) + Element::loc($m->file()->canonicalUrl()) ); } diff --git a/src/File.php b/src/File.php index 6c88eb21..536e0d46 100644 --- a/src/File.php +++ b/src/File.php @@ -6,19 +6,25 @@ use DirectoryIterator; -use JoshBruce\Site\FileSystem; +use JoshBruce\Site\FileSystemInterface; class File { private string $contentFileName = '/content.md'; - public static function at(string $localPath): File + private string $contents = ''; + + private string $mimetype = ''; + + public static function at(string $localPath, FileSystemInterface $in): File { - return new File($localPath); + return new File($localPath, $in); } - private function __construct(private string $localPath) - { + private function __construct( + private string $localPath, + private FileSystemInterface $fileSystem + ) { } public function isNotMarkdown(): bool @@ -60,7 +66,7 @@ public function path(bool $full = true): string } // TODO: test and verify used - returning empty string not an option. return str_replace( - $this->contentRoot(), + $this->fileSystem()->publicRoot(), '', $this->localPath ); @@ -76,39 +82,48 @@ public function up(): File $parts = explode('/', $this->localPath); $parts = array_slice($parts, 0, -2); // remove file name and one folder. $localPath = implode('/', $parts); - return File::at($localPath . $this->contentFileName); + return File::at( + $localPath . $this->contentFileName, + $this->fileSystem() + ); } public function contents(): string { - $contents = file_get_contents($this->path()); - if ($contents === false) { - return ''; + if (strlen($this->contents) === 0) { + $contents = file_get_contents($this->path()); + if ($contents === false) { + return ''; + } + $this->contents = $contents; } - return $contents; + return $this->contents; } public function mimetype(): string { - $type = mime_content_type($this->path()); - if (is_bool($type) and $type === false) { - return ''; - } + if (strlen($this->mimetype) === 0) { + $type = mime_content_type($this->path()); + if (is_bool($type) and $type === false) { + return ''; + } - if ($type === 'text/plain') { - $extensionMap = [ - 'md' => 'text/html', - 'css' => 'text/css', - 'js' => 'text/javascript', - 'xml' => 'application/xml' - ]; + if ($type === 'text/plain') { + $extensionMap = [ + 'md' => 'text/html', + 'css' => 'text/css', + 'js' => 'text/javascript', + 'xml' => 'application/xml' + ]; - $parts = explode('.', $this->path()); - $extension = array_pop($parts); + $parts = explode('.', $this->path()); + $extension = array_pop($parts); - $type = $extensionMap[$extension]; + $type = $extensionMap[$extension]; + } + $this->mimetype = $type; } - return $type; + return $this->mimetype; } public function canonicalUrl(): string @@ -138,14 +153,15 @@ public function children(string $filesNamed): array $folderName = array_pop($parts); $files[$folderName] = File::at( - $fullPathToFolder . '/' . $filesNamed + $fullPathToFolder . '/' . $filesNamed, + $this->fileSystem() ); } return $files; } - private function contentRoot(): string + private function fileSystem(): FileSystemInterface { - return FileSystem::publicRoot(); + return $this->fileSystem; } } diff --git a/src/FileSystem.php b/src/FileSystem.php index e82af58d..3d098e02 100644 --- a/src/FileSystem.php +++ b/src/FileSystem.php @@ -8,16 +8,38 @@ use Symfony\Component\Finder\Finder; -class FileSystem +use JoshBruce\Site\FileSystemInterface; + +class FileSystem implements FileSystemInterface { - public static function publicRoot(): string + public static function init(): static + { + return new static(static::projectRoot()); + } + + public static function projectRoot(): string + { + $dir = __DIR__; + $parts = explode('/', $dir); + $parts = array_slice($parts, 0, -1); + return implode('/', $parts); + } + + final private function __construct(protected string $projectRoot) { - return FileSystem::contentRoot() . '/public'; } - public static function contentRoot(): string + public function hasRequiredFolders(): bool { - $parts = explode('/', self::projectRoot()); + return file_exists($this->contentRoot()) and + file_exists($this->publicRoot()) and + is_dir($this->contentRoot()) and + is_dir($this->publicRoot()); + } + + public function contentRoot(): string + { + $parts = explode('/', static::projectRoot()); $parts[] = 'content'; $base = implode('/', $parts); if (str_ends_with($base, '/')) { @@ -26,41 +48,42 @@ public static function contentRoot(): string return $base; } - public static function projectRoot(): string + public function publicRoot(): string { - $dir = __DIR__; - $parts = explode('/', $dir); - $parts = array_slice($parts, 0, -1); - return implode('/', $parts); + return $this->contentRoot() . '/public'; } - public static function finder(): Finder + public function publishedContentFinder(): Finder { - $finder = new Finder(); - return $finder->ignoreVCS(false) - ->ignoreUnreadableDirs() - ->ignoreDotFiles(false) - ->ignoreVCSIgnored(true) - ->notName('.gitignore') - ->files() - ->filter(fn($f) => self::isPublished($f)) - ->in(self::publicRoot()); + return $this->finder()->in($this->publicRoot())->name('content.md') + ->filter(fn($f) => $this->isPublished($f)); } - private static function isPublished(SplFileInfo $finderFile): bool + private function relativePath(string $path): string { - return ! self::isDraft($finderFile); + return str_replace($this->contentRoot(), '', $path); } - private static function isDraft(SplFileInfo $finderFile): bool + private function isPublished(SplFileInfo $finderFile): bool + { + return ! $this->isDraft($finderFile); + } + + private function isDraft(SplFileInfo $finderFile): bool { $filePath = (string) $finderFile; - $relativePath = self::relativePath($filePath); + $relativePath = $this->relativePath($filePath); return str_contains($relativePath, '_'); } - private static function relativePath(string $path): string + private function finder(): Finder { - return str_replace(self::contentRoot(), '', $path); + $finder = new Finder(); + return $finder->ignoreVCS(false) + ->ignoreUnreadableDirs() + ->ignoreDotFiles(false) + ->ignoreVCSIgnored(true) + ->notName('.gitignore') + ->files(); } } diff --git a/src/FileSystemInterface.php b/src/FileSystemInterface.php new file mode 100644 index 00000000..5a638bcd --- /dev/null +++ b/src/FileSystemInterface.php @@ -0,0 +1,22 @@ +serverGlobals()->isMissingAppEnv()) { - return true; - } + public static function with( + ServerGlobals $serverGlobals, + FileSystemInterface $in + ): HttpRequest { + return new HttpRequest($serverGlobals, $in); + } - if ($this->serverGlobals()->appEnvIsNot('production')) { + private function __construct( + private ServerGlobals $serverGlobals, + private FileSystemInterface $fileSystem + ) { + if ($this->serverGlobals()->appEnv() !== 'production') { // use Whoops! for error display $errorHandler = new ErrorHandler(); $errorHandler->pushHandler( @@ -47,17 +50,37 @@ public function isMissingRequiredValues(): bool ); $errorHandler->register(); } - return false; + } + + public function isMissingRequiredValues(): bool + { + return $this->serverGlobals()->isMissingRequiredValues(); } public function isUnsupportedMethod(): bool { - return ! $this->isSupportedMethod(); + $requestMethod = strtoupper($this->psrRequest()->getMethod()); + $isSupported = in_array($requestMethod, $this->supportedMethods()); + + return ! $isSupported; } public function isNotFound(): bool { - return ! $this->isFound(); + $isFound = file_exists($this->localPath()) and + is_file($this->localPath()); + return ! $isFound; + } + + public function localFile(): File + { + if (! isset($this->localFile)) { + $this->localFile = File::at( + localPath: $this->localPath(), + in: $this->fileSystem() + ); + } + return $this->localFile; } public function isFile(): bool @@ -67,7 +90,7 @@ public function isFile(): bool public function isSitemap(): bool { - return $this->isFile() and $this->possibleFileName() === 'sitemap.xml'; + return $this->possibleFileName() === 'sitemap.xml'; } public function isNotSitemap(): bool @@ -75,52 +98,46 @@ public function isNotSitemap(): bool return ! $this->isSitemap(); } - public function localFile(): File + public function fileSystem(): FileSystemInterface { - return File::at(localPath: $this->localPath()); - } - - private function isFound(): bool - { - return file_exists($this->localPath()) and is_file($this->localPath()); + return $this->fileSystem; } private function localPath(): string { if (empty($this->localPath)) { - $possibleFileName = $this->possibleFileName(); - $relativePath = $this->uriPath(); - if (empty($possibleFileName)) { - $relativePath = $this->uriPath() . '/content.md'; - - // } elseif (str_contains($relativePath, '.xml')) { - // $relativePath = str_replace('.xml', '.md', $relativePath); - + $relativePath = $this->psrPath(); + if (empty($this->possibleFileName())) { + $relativePath = $this->psrPath() . '/content.md'; } if (! str_starts_with($relativePath, '/')) { $relativePath = "/{$relativePath}"; } - $root = FileSystem::contentRoot(); + $root = $this->fileSystem()->publicRoot(); - $this->localPath = "{$root}/public{$relativePath}"; + $this->localPath = "{$root}{$relativePath}"; } return $this->localPath; } private function possibleFileName(): string { - $parts = explode('/', $this->uriPath()); - $lastPart = array_slice($parts, -1); - $possibleFileName = array_shift($lastPart); - if ( - $possibleFileName === null or - ! str_contains($possibleFileName, '.') - ) { - return ''; + if (strlen($this->possibleFileName) === 0) { + $parts = explode('/', $this->psrPath()); + $lastPart = array_slice($parts, -1); + $possibleFileName = array_shift($lastPart); + if ( + $possibleFileName === null or + ! str_contains($possibleFileName, '.') + ) { + return ''; + } + + $this->possibleFileName = $possibleFileName; } - return $possibleFileName; + return $this->possibleFileName; } /** @@ -131,15 +148,9 @@ private function supportedMethods(): array return ['GET']; } - private function isSupportedMethod(): bool - { - $requestMethod = strtoupper($this->psrRequest()->getMethod()); - return in_array($requestMethod, $this->supportedMethods()); - } - private function serverGlobals(): ServerGlobals { - return ServerGlobals::init(); + return $this->serverGlobals; } private function psrRequest(): RequestInterface @@ -158,17 +169,12 @@ private function psrRequest(): RequestInterface return $this->psrRequest; } - private function uriPath(): string + private function psrPath(): string { - $uriPath = $this->uri()->getPath(); - if ($uriPath === '/') { - $uriPath = ''; + $psrPath = $this->psrRequest()->getUri()->getPath(); + if ($psrPath === '/') { + $psrPath = ''; } - return $uriPath; - } - - private function uri(): UriInterface - { - return $this->psrRequest()->getUri(); + return $psrPath; } } diff --git a/src/HttpResponse.php b/src/HttpResponse.php index 7f9e7d36..3a753f64 100644 --- a/src/HttpResponse.php +++ b/src/HttpResponse.php @@ -14,7 +14,6 @@ use Eightfold\HTMLBuilder\Document; use Eightfold\HTMLBuilder\Element; -use JoshBruce\Site\FileSystem; use JoshBruce\Site\File; use JoshBruce\Site\Content\Markdown; @@ -38,13 +37,13 @@ private function __construct(private HttpRequest $request) public function statusCode(): int { - if ($this->request->isMissingRequiredValues()) { + if ($this->request()->isMissingRequiredValues()) { return 500; - } elseif ($this->request->isUnsupportedMethod()) { + } elseif ($this->request()->isUnsupportedMethod()) { return 405; - } elseif ($this->request->isNotFound()) { + } elseif ($this->request()->isNotFound()) { return 404; } @@ -58,33 +57,34 @@ public function headers(): array { $headers = []; if ($this->statusCode() === 200) { - $headers['Content-Type'] = $this->request->localFile()->mimeType(); + $headers['Content-Type'] = $this->request()->localFile()->mimeType(); } elseif ($this->statusCode() === 404) { $headers['Content-Type'] = 'text/html'; } - return $headers; } public function body(): string { - $localFile = $this->request->localFile(); + $localFile = $this->request()->localFile(); if ( $this->statusCode() === 200 and $localFile->isNotMarkdown() and - $this->request->isNotSitemap() + $this->request()->isNotSitemap() ) { return $localFile->path(); } elseif ($this->statusCode() === 404) { - $localPath = FileSystem::contentRoot() . '/public/error-404.md'; - $localFile = File::at($localPath); + $localPath = $this->request()->fileSystem()->publicRoot() . + '/error-404.md'; + $localFile = File::at($localPath, $this->request()->fileSystem()); } elseif ($this->statusCode() === 405) { - $localPath = FileSystem::contentRoot() . '/public/error-405.md'; - $localFile = File::at($localPath); + $localPath = $this->request()->fileSystem()->publicRoot() . + '/error-405.md'; + $localFile = File::at($localPath, $this->request()->fileSystem()); } @@ -92,16 +92,17 @@ public function body(): string $pageTitle = ''; $html = ''; if ($localFile->isMarkdown()) { - $markdown = Markdown::for(file: $localFile); + $markdown = Markdown::for( + file: $localFile, + in: $this->request()->fileSystem() + ); $template = $markdown->frontMatter()->template(); $pageTitle = $markdown->pageTitle(); $html = $markdown->html(); - } - if ($this->request->isSitemap()) { - return Sitemap::create(); - + if ($this->request()->isSitemap()) { + return Sitemap::create($this->request()->fileSystem()); } return Document::create( @@ -138,7 +139,7 @@ public function body(): string $html )->props('typeof BlogPosting', 'vocab https://schema.org/'), Element::a('top')->props('href #content-top', 'id go-to-top'), - Navigation::create('main.md'), + Navigation::create('main.md', $this->request()->fileSystem()), Element::footer( Element::p( 'Copyright © 2004–' . date('Y') . ' Joshua C. Bruce. ' . @@ -167,8 +168,8 @@ public function psrResponse(): ResponseInterface $body = $this->body(); $stream = $psr17Factory->createStream($body); if ( - $this->request->isFile() and - $this->request->isNotSitemap() + $this->request()->isFile() and + $this->request()->isNotSitemap() ) { $stream = $psr17Factory->createStreamFromFile($body); } @@ -181,4 +182,9 @@ public function psrResponse(): ResponseInterface } return $this->psrResponse; } + + private function request(): HttpRequest + { + return $this->request; + } } diff --git a/src/PageComponents/LogList.php b/src/PageComponents/LogList.php index 687571c5..75ca92ca 100644 --- a/src/PageComponents/LogList.php +++ b/src/PageComponents/LogList.php @@ -4,6 +4,7 @@ namespace JoshBruce\Site\PageComponents; +use JoshBruce\Site\FileSystemInterface; use JoshBruce\Site\File; use Eightfold\HTMLBuilder\Element; @@ -12,8 +13,10 @@ class LogList { - public static function create(File $file): string - { + public static function create( + File $file, + FileSystemInterface $fileSystem + ): string { $fileSubfolders = $file->children(filesNamed: 'content.md'); if (count($fileSubfolders) === 0) { return ''; @@ -23,7 +26,7 @@ public static function create(File $file): string $logLinks = []; foreach ($fileSubfolders as $key => $file) { if (! str_starts_with(strval($key), '_') and $file->found()) { - $markdown = Markdown::for($file); + $markdown = Markdown::for($file, $fileSystem); $linkPath = str_replace( '/content.md', diff --git a/src/PageComponents/Navigation.php b/src/PageComponents/Navigation.php index 93925813..325d5909 100644 --- a/src/PageComponents/Navigation.php +++ b/src/PageComponents/Navigation.php @@ -6,24 +6,26 @@ use Eightfold\HTMLBuilder\Element; -use JoshBruce\Site\FileSystem; +use JoshBruce\Site\FileSystemInterface; use JoshBruce\Site\File; use JoshBruce\Site\Content\Markdown; class Navigation { - public static function create(string $fileName): string - { - $contentRoot = FileSystem::contentRoot(); + public static function create( + string $fileName, + FileSystemInterface $fileSystem + ): string { + $contentRoot = $fileSystem->contentRoot(); $navigationPath = $contentRoot . '/navigation'; $filePath = $navigationPath . '/' . $fileName; - $file = File::at($filePath); + $file = File::at($filePath, $fileSystem); if ($file->isNotFound()) { return ''; } - $html = Markdown::for($file)->html(); + $html = Markdown::for($file, $fileSystem)->html(); return Element::nav($html)->props('id main-nav')->build(); } diff --git a/src/PageComponents/OriginalContentNotice.php b/src/PageComponents/OriginalContentNotice.php index 067671a2..01a01c30 100644 --- a/src/PageComponents/OriginalContentNotice.php +++ b/src/PageComponents/OriginalContentNotice.php @@ -7,19 +7,21 @@ use Eightfold\HTMLBuilder\Element; use JoshBruce\Site\File; -use JoshBruce\Site\FileSystem; +use JoshBruce\Site\FileSystemInterface; use JoshBruce\Site\Content\Markdown; use JoshBruce\Site\Content\FrontMatter; class OriginalContentNotice { - public static function create(FrontMatter $frontMatter): string - { - $contentRoot = FileSystem::contentRoot(); + public static function create( + FrontMatter $frontMatter, + FileSystemInterface $fileSystem + ): string { + $contentRoot = $fileSystem->contentRoot(); $noticesRoot = $contentRoot . '/notices'; - $file = File::at($noticesRoot . '/original.md'); + $file = File::at($noticesRoot . '/original.md', $fileSystem); if ($file->isNotFound()) { return ''; } @@ -27,7 +29,7 @@ public static function create(FrontMatter $frontMatter): string $original = $frontMatter->original(); list($href, $platform) = explode(' ', $original, 2); - $body = Markdown::for($file)->body(); + $body = Markdown::for($file, $fileSystem)->body(); $matches = []; $search = '/{!!platformlink!!}/'; diff --git a/src/ServerGlobals.php b/src/ServerGlobals.php index 0f11fd80..980c1474 100644 --- a/src/ServerGlobals.php +++ b/src/ServerGlobals.php @@ -6,6 +6,11 @@ class ServerGlobals { + /** + * @var array + */ + private array $globals = []; + public static function init(): ServerGlobals { return new ServerGlobals(); @@ -13,49 +18,56 @@ public static function init(): ServerGlobals private function __construct() { + $this->globals = $_SERVER; } - public function appEnvIsNot(string $value): bool + public function withRequestUri(string $uri): ServerGlobals { - return $this->appEnv() !== $value; + $this->globals = []; + + $_SERVER['REQUEST_URI'] = $uri; + return $this; } - public function isMissingAppEnv(): bool + public function requestUri(): string { - return ! $this->hasAppEnv(); + $globals = $this->globals(); + return strval($globals['REQUEST_URI']); } - public function isMissingAppUrl(): bool + public function withRequestMethod(string $method): ServerGlobals { - return ! $this->hasAppUrl(); + $this->globals = []; + + $_SERVER['REQUEST_METHOD'] = $method; + return $this; } - public function appUrl(): string + public function requestMethod(): string { - if ($this->hasAppUrl()) { - $globals = $this->globals(); - return strval($globals['APP_URL']); - } - return ''; + $globals = $this->globals(); + return strval($globals['REQUEST_METHOD']); } - private function appEnv(): string + public function isMissingRequiredValues(): bool { - if ($this->hasAppEnv()) { - $globals = $this->globals(); - return strval($globals['APP_ENV']); - } - return ''; + return ! $this->hasRequiredValues(); } - private function hasAppEnv(): bool + private function hasRequiredValues(): bool { - return array_key_exists('APP_ENV', $this->globals()); + $globals = $this->globals(); + return array_key_exists('APP_ENV', $globals) and + array_key_exists('APP_URL', $globals); } - private function hasAppUrl(): bool + public function appEnv(): string { - return array_key_exists('APP_URL', $this->globals()); + if ($this->hasRequiredValues()) { + $globals = $this->globals(); + return strval($globals['APP_ENV']); + } + return ''; } /** @@ -63,6 +75,9 @@ private function hasAppUrl(): bool */ private function globals(): array { - return $_SERVER; + if (count($this->globals) === 0) { + $this->globals = $_SERVER; + } + return $this->globals; } } diff --git a/src/SiteStatic/Generator.php b/src/SiteStatic/Generator.php index 95687883..87e9bf1c 100644 --- a/src/SiteStatic/Generator.php +++ b/src/SiteStatic/Generator.php @@ -15,6 +15,7 @@ use League\Flysystem\Local\LocalFilesystemAdapter; use League\Flysystem\Filesystem as LeagueFilesystem; +use JoshBruce\Site\ServerGlobals; use JoshBruce\Site\FileSystem; use JoshBruce\Site\HttpResponse; @@ -39,15 +40,14 @@ private function __construct( private OutputInterface $output, private string $destination = '' ) { - $projectRoot = FileSystem::projectRoot(); + $projectRoot = FileSystem::init()->projectRoot(); + // $projectRoot = FileSystem::projectRoot(); Dotenv::createImmutable($projectRoot)->load(); - if (isset($_SERVER['APP_ENV'])) { - $this->isNotTesting = $_SERVER['APP_ENV'] !== 'testing'; - } + // $this->isNotTesting = ServerGlobals::init()->appEnv() !== 'test'; - $this->contentRoot = FileSystem::contentRoot() . '/public'; + $this->contentRoot = FileSystem::init()->publicRoot(); if (strlen($destination) === 0) { $this->destination = $projectRoot . '/site-static-html/public'; @@ -83,34 +83,35 @@ private function compileContentFileFor(string $contentPath): void $parts = array_slice($parts, 0, -1); $requestUri = implode('/', $parts); - $_SERVER['REQUEST_URI'] = $requestUri; + $globals = ServerGlobals::init()->withRequestUri($requestUri) + ->withRequestMethod('GET'); if (str_contains($destinationPath, '/error-404.html')) { - $_SERVER['REQUEST_URI'] = '/low/probability/of/ex/is/ting'; + $globals = $globals->withRequestUri('/low/prob/a/bil/it/ee'); } elseif (str_contains($destinationPath, '/error-405.html')) { - $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $globals = $globals->withRequestMethod('DELETE'); } elseif (strlen($requestUri) === 0) { - $_SERVER['REQUEST_URI'] = '/'; + $globals = $globals->withRequestUri('/'); } - // $_SERVER['REQUEST_URI'] = (strlen($requestUri) === 0) - // ? '/' - // : $requestUri; + $fileSystem = FileSystem::init(); - $html = HttpResponse::from(request: HttpRequest::fromGlobals())->body(); + $html = HttpResponse::from( + request: HttpRequest::with(serverGlobals: $globals, in: $fileSystem) + )->body(); $this->leagueFileSystem()->write($destinationPath, $html); - // $this->sourceFileConvertedMessage($contentPath, $destinationPath); + $this->sourceFileConvertedMessage($contentPath, $destinationPath); } private function copyFileFor(string $path): void { $destinationPath = $this->fileDestinationPathFor($path); $this->leagueFileSystem()->copy($path, $destinationPath); - // $this->sourceFileCopiedMessage($path, $destinationPath); + $this->sourceFileCopiedMessage($path, $destinationPath); } private function contentDestinationPathFor(string $path): string diff --git a/tests/FileSystemTest.php b/tests/FileSystemTest.php index 3cb762b6..392b5cce 100644 --- a/tests/FileSystemTest.php +++ b/tests/FileSystemTest.php @@ -1,181 +1,33 @@ projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); -// -// serverGlobals(); -// -// $this->contentRoot = $this->projectRoot . '/content'; -// }); -// -// test('content folder does exist', function() { -// expect( -// FileSystem::contentFolderIsMissing() -// )->toBeFalse(); -// })->group('filesystem'); -// -// it('can initialize folders', function() { -// expect( -// FileSystem::public()->path() -// )->toBeString()->toBe( -// "{$this->contentRoot}/public" -// ); -// -// expect( -// FileSystem::navigation()->path() -// )->toBeString()->toBe( -// "{$this->contentRoot}/navigation" -// ); -// })->group('filesystem'); -// -// // it('can return folder tree', function() { -// // $this->assertEquals( -// // FileSystem::init( -// // $this->contentRoot, -// // 'public', -// // 'finances', -// // 'investment-policy' -// // )->folderStack(), -// // [ -// // FileSystem::init( -// // $this->contentRoot, -// // 'public', -// // 'finances', -// // 'investment-policy' -// // ), -// // FileSystem::init( -// // $this->contentRoot, -// // 'public', -// // 'finances' -// // ), -// // FileSystem::init( -// // $this->contentRoot, -// // 'public' -// // ), -// // FileSystem::init( -// // $this->contentRoot -// // ) -// // ] -// // ); -// // -// // $this->assertEquals( -// // FileSystem::init($this->contentRoot) -// // ->with(folderPath: '/sub-folder') -// // ->folderStack(fileName: 'content.md'), -// // [ -// // FileSystem::init($this->contentRoot)->with( -// // folderPath: '/sub-folder', -// // fileName: 'content.md' -// // ), -// // FileSystem::init($this->contentRoot) -// // ->with(folderPath: '', fileName: 'content.md') -// // ] -// // ); -// // })->group('filesystem', 'focus'); -// // -// // it('can determine if path is content root', function() { -// // expect( -// // FileSystem::init($this->contentRoot)->isRoot() -// // )->toBeTrue(); -// // -// // expect( -// // FileSystem::init($this->contentRoot, '/not-there')->isRoot() -// // )->toBeFalse(); -// // -// // expect( -// // FileSystem::init($this->contentRoot)->isNotRoot() -// // )->toBeFalse(); -// // -// // expect( -// // FileSystem::init($this->contentRoot, '/not-there')->isNotRoot() -// // )->toBeTrue(); -// // })->group('filesystem'); -// -// // it('can determine if path exists', function() { -// // expect( -// // FileSystem::init($this->contentRoot)->found() -// // )->toBeTrue(); -// // -// // expect( -// // FileSystem::init($this->contentRoot . '/not-there')->found() -// // )->toBeFalse(); -// // -// // expect( -// // FileSystem::init($this->contentRoot)->notFound() -// // )->toBeFalse(); -// // -// // expect( -// // FileSystem::init($this->contentRoot . '/not-there')->notFound() -// // )->toBeTrue(); -// // })->group('filesystem'); -// // -// // it('can detect whether the root folder exists', function() { -// // expect( -// // FileSystem::init($this->contentRoot)->rootFolderIsMissing() -// // )->toBeFalse(); -// // -// // expect( -// // FileSystem::init($this->contentRoot . '/not-there')->rootFolderIsMissing() -// // )->toBeTrue(); -// // })->group('filesystem'); -// // -// // it('has correct file path', function() { -// // expect( -// // FileSystem::init($this->contentRoot, 'css', 'main.min.css')->path() -// // )->toBe( -// // "{$this->contentRoot}/css/main.min.css" -// // ); -// // -// // expect( -// // FileSystem::init($this->contentRoot)->navigation('main.md')->path() -// // )->toBe( -// // "{$this->contentRoot}/navigation/main.md" -// // ); -// // -// // expect( -// // FileSystem::init($this->contentRoot)->messages('original.md')->path() -// // )->toBe( -// // "{$this->contentRoot}/messages/original.md" -// // ); -// // -// // expect( -// // FileSystem::init($this->contentRoot)->messages()->path() -// // )->toBe( -// // "{$this->contentRoot}/messages" -// // ); -// // -// // expect( -// // FileSystem::init($this->contentRoot, 'public', 'legal', 'content.md') -// // ->path(false) -// // )->toBe( -// // '/public/legal/content.md' -// // ); -// // })->group('filesystem'); -// // -// // it('has correct mimetypes', function() { -// // // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/ -// // // MIME_types#textjavascript -// // expect( -// // FileSystem::init($this->contentRoot, 'gulpfile.js')->mimetype() -// // )->toBe( -// // 'text/javascript' -// // ); -// // -// // expect( -// // FileSystem::init($this->contentRoot, 'css', 'main.min.css')->mimetype() -// // )->toBe( -// // 'text/css' -// // ); -// // -// // expect( -// // FileSystem::init($this->contentRoot, 'public', 'content.md')->mimetype() -// // )->toBe( -// // 'text/html' -// // ); -// // })->group('filesystem'); + +use JoshBruce\Site\Tests\TestFileSystem; + +beforeEach(function() { + $this->projectRoot = TestFileSystem::projectRoot(); +}); + +it('can get published content', function() { + expect( + count( + TestFileSystem::init()->publishedContentFinder() + ) + )->toBeInt()->toBe(2); +})->group('filesystem'); + +it('has required folders', function() { + expect( + TestFileSystem::init()->hasRequiredFolders() + )->toBeTrue(); + + expect( + TestFileSystem::init()->contentRoot() + )->toBe( + $this->projectRoot . '/content' + ); + + expect( + TestFileSystem::init()->publicRoot() + )->toBe( + $this->projectRoot . '/content/public' + ); +})->group('filesystem'); diff --git a/tests/FileTest.php b/tests/FileTest.php index 956a10c5..1311aafb 100644 --- a/tests/FileTest.php +++ b/tests/FileTest.php @@ -4,31 +4,21 @@ use JoshBruce\Site\File; -use JoshBruce\Site\FileSystem; +use JoshBruce\Site\Tests\TestFileSystem; test('can generate canonical URL', function() { - expect( - File::at(FileSystem::publicRoot() . '/content.md')->canonicalUrl() - )->toBe( - 'https://joshbruce.com' - ); + $fileSystem = TestFileSystem::init(); + $publicRoot = $fileSystem->publicRoot(); expect( - File::at(FileSystem::publicRoot())->canonicalUrl() - )->toBe( + File::at($publicRoot . '/content.md', $fileSystem)->canonicalUrl() + )->toBe( 'https://joshbruce.com' - ); + ); expect( - File::at(FileSystem::publicRoot() . '/web-development')->canonicalUrl() - )->toBe( - 'https://joshbruce.com/web-development' - ); - - expect( - File::at(FileSystem::publicRoot() . '/web-development/content.md') - ->canonicalUrl() + File::at($publicRoot, $fileSystem)->canonicalUrl() )->toBe( - 'https://joshbruce.com/web-development' + 'https://joshbruce.com' ); })->group('file'); diff --git a/tests/GneratorTest.php b/tests/GneratorTest.php new file mode 100644 index 00000000..d81d41a7 --- /dev/null +++ b/tests/GneratorTest.php @@ -0,0 +1,3 @@ +headers() )->toBe( ['Content-Type' => 'text/html'] ); - serverGlobals('/assets/css/main.min.css'); - expect( HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init() + ->withRequestUri('/assets/css/main.min.css'), + TestFileSystem::init() + ) )->headers() )->toBe( ['Content-Type' => 'text/css'] ); -})->group('response'); +})->group('response', 'request'); test('expected titles', function() { - serverGlobals(); - $body = HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init()->withRequestUri('/'), + TestFileSystem::init() + ) )->body(); expect( - str_contains($body, "Josh Bruce's personal site") + str_contains($body, "Test content root") )->toBeTrue(); - serverGlobals('/finances'); - $body = HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init()->withRequestUri('/published-sub'), + TestFileSystem::init() + ) )->body(); expect( str_contains( $body, - "Finances | Josh Bruce's personal site" + "Sub-folder content title | Test content root" ) )->toBeTrue(); - serverGlobals('/something-missing'); - $body = HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init()->withRequestUri('/something/invalid'), + TestFileSystem::init() + ) )->body(); expect( @@ -63,94 +73,54 @@ "Page not found" ) )->toBeTrue(); -})->group('response'); +})->group('response', 'request'); test('expected status codes', function() { - serverGlobals(); - expect( HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init()->withRequestUri('/'), + TestFileSystem::init() + ) )->statusCode() )->toBeInt()->toBe( 200 ); - unset($_SERVER['APP_ENV']); - expect( HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init()->withRequestUri('/something/invalid'), + TestFileSystem::init() + ) )->statusCode() )->toBeInt()->toBe( - 500 + 404 ); - serverGlobals(); - - $_SERVER['REQUEST_METHOD'] = 'post'; - expect( HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init()->withRequestMethod('post'), + TestFileSystem::init() + ) )->statusCode() )->toBeInt()->toBe( 405 ); - serverGlobals('/not-valid'); + unset($_SERVER['APP_URL']); expect( HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init(), + TestFileSystem::init() + ) )->statusCode() )->toBeInt()->toBe( - 404 + 500 ); -})->group('response'); -// -// test('can check request is valid', function() { -// serverGlobals(); -// -// expect( -// HttpRequest::init()->isMissingRequiredValues() -// )->toBeFalse(); -// -// expect( -// HttpRequest::init()->isUnsupportedMethod() -// )->toBeFalse(); -// -// expect( -// HttpRequest::init()->isNotFound() -// )->toBeFalse(); -// -// serverGlobals('/not-valid'); -// unset($_SERVER['APP_ENV']); -// $_SERVER['REQUEST_METHOD'] = 'post'; -// -// expect( -// HttpRequest::init()->isMissingRequiredValues() -// )->toBeTrue(); -// -// expect( -// HttpRequest::init()->isUnsupportedMethod() -// )->toBeTrue(); -// -// expect( -// HttpRequest::init()->isNotFound() -// )->toBeTrue(); -// })->group('request'); -// -// it('uses server globals', function() { -// serverGlobals(); -// -// expect( -// ServerGlobals::init()->hasAppEnv() -// )->toBeTrue(); -// -// unset($_SERVER['APP_ENV']); -// -// expect( -// ServerGlobals::init()->hasAppEnv() -// )->toBeFalse(); -// })->group('globals'); + + $_SERVER['APP_URL'] = 'http://jb-site.test'; +})->group('response', 'request'); diff --git a/tests/Pest.php b/tests/Pest.php index 0ac9c7af..19377289 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -11,8 +11,6 @@ | */ -// uses(Tests\TestCase::class)->in('Feature'); - /* |-------------------------------------------------------------------------- | Functions @@ -23,29 +21,3 @@ | global functions to help you to reduce the number of lines of code in your test files. | */ - -use JoshBruce\Site\Environment; -use JoshBruce\Site\Server; - -function environment(string $requestUri = '/'): Environment -{ - return Environment::init(server($requestUri)); -} - -function server(string $requestUri = '/'): Server -{ - return Server::init(serverGlobals($requestUri)); -} - -function serverGlobals(string $requestUri = '/'): array -{ - $_SERVER['APP_ENV'] = 'test'; - // $_SERVER['CONTENT_UP'] = 0; - // $_SERVER['CONTENT_FOLDER'] = '/tests/test-content/content'; - // $_SERVER['REQUEST_SCHEME'] = 'http'; - // $_SERVER['HTTP_HOST'] = 'testing.com'; - $_SERVER['REQUEST_URI'] = $requestUri; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - return $_SERVER; -} diff --git a/tests/ServerGlobalsTest.php b/tests/ServerGlobalsTest.php new file mode 100644 index 00000000..744a8e91 --- /dev/null +++ b/tests/ServerGlobalsTest.php @@ -0,0 +1,46 @@ +withRequestUri('/something')->requestUri() + )->toBe( + '/something' + ); + + expect( + ServerGlobals::init()->withRequestMethod('GET')->requestMethod() + )->toBe( + 'GET' + ); + + expect( + ServerGlobals::init()->withRequestMethod('POST')->requestMethod() + )->toBe( + 'POST' + ); + + expect( + ServerGlobals::init()->isMissingRequiredValues() + )->toBeFalse(); +})->group('server-globals'); + +test('only ServerGlobals references $_SERVER', function() { + $finder = new Finder(); + $found = $finder->ignoreVCS(false)->files()->name('*.php')->in( + FileSystem::projectRoot() . '/src' + )->contains('$_SERVER'); + + foreach ($found as $f) { + $result = str_ends_with($f->getPathname(), 'ServerGlobals.php'); + if (! $result){ + var_dump($f->getPathname()); + } + $this->assertTrue($result); + } +})->group('server-globals'); diff --git a/tests/ServerTest.php b/tests/ServerTest.php deleted file mode 100644 index d18626be..00000000 --- a/tests/ServerTest.php +++ /dev/null @@ -1,65 +0,0 @@ -projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); -// -// serverGlobals(); -// -// $this->contentRoot = $this->projectRoot . $_SERVER['CONTENT_FOLDER']; -// }); -// -// it('has expected project roo', function() { -// expect( -// Server::projectRoot() -// )->toBeString()->toBe( -// $this->projectRoot -// ); -// })->group('server'); -// -// it('has expected file name and content root', function() { -// expect( -// Server::init(serverGlobals(), $this->projectRoot)->requestFileName() -// )->toBe( -// '' -// ); -// -// expect( -// Server::init(serverGlobals(), $this->projectRoot)->contentRoot() -// )->toBe( -// __DIR__ . '/test-content/content' -// ); -// })->group('server'); -// -// it('limits request methods', function() { -// expect( -// Server::init(serverGlobals(), $this->projectRoot) -// ->isRequestingUnsupportedMethod() -// )->toBeFalse(); -// -// $serverGlobals = serverGlobals(); -// $serverGlobals['REQUEST_METHOD'] = 'INVALID'; -// -// expect( -// Server::init($serverGlobals, $this->projectRoot) -// ->isRequestingUnsupportedMethod() -// )->toBeTrue(); -// })->group('server'); -// -// it('has required variables', function() { -// expect( -// Server::init(serverGlobals(), $this->projectRoot) -// ->isMissingRequiredValues() -// )->toBeFalse(); -// -// $serverGlobals = serverGlobals(); -// unset($serverGlobals['CONTENT_UP']); -// -// expect( -// Server::init($serverGlobals, $this->projectRoot) -// ->isMissingRequiredValues() -// )->toBeTrue(); -// })->group('server'); diff --git a/tests/SitemapTest.php b/tests/SitemapTest.php index edadd0b3..49375aee 100644 --- a/tests/SitemapTest.php +++ b/tests/SitemapTest.php @@ -7,11 +7,21 @@ use JoshBruce\Site\HttpRequest; use JoshBruce\Site\HttpResponse; +use JoshBruce\Site\ServerGlobals; + +use JoshBruce\Site\Tests\TestFileSystem; + + it('can respond to sitemap request', function() { - serverGlobals('sitemap.xml'); + // serverGlobals('sitemap.xml'); $xml = HttpResponse::from( - request: HttpRequest::fromGlobals() + request: HttpRequest::with( + ServerGlobals::init() + ->withRequestUri('/sitemap.xml') + ->withRequestMethod('GET'), // TODO: indicates fragile tests + TestFileSystem::init() + ) ); expect( @@ -34,7 +44,7 @@ $xml->body() )->toBe(<< - https://joshbruce.comhttps://joshbruce.com/design-your-lifehttps://joshbruce.com/design-your-life/motivatorshttps://joshbruce.com/finances/budgetinghttps://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210301https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210315https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210401https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210415https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210501https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210515https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210601https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210615https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210701https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210715https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210801https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210815https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210901https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20210915https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20211001https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20211015https://joshbruce.com/finances/building-wealth-paycheck-to-paycheck/20211101https://joshbruce.com/finances/building-wealth-paycheck-to-paycheckhttps://joshbruce.com/financeshttps://joshbruce.com/finances/investment-policyhttps://joshbruce.com/health-and-wellnesshttps://joshbruce.com/legalhttps://joshbruce.com/software-developmenthttps://joshbruce.com/software-development/why-dont-you-usehttps://joshbruce.com/web-development/2021-site-in-depthhttps://joshbruce.com/web-developmenthttps://joshbruce.com/web-development/modern-web-developmenthttps://joshbruce.com/web-development/my-history-on-the-webhttps://joshbruce.com/web-development/on-constraintshttps://joshbruce.com/web-development/on-constraints/internet-bandwidthhttps://joshbruce.com/web-development/refactoring-re-engineering-and-rebuildinghttps://joshbruce.com/web-development/site-statshttps://joshbruce.com/web-development/static-dynamic-and-interactive + https://joshbruce.comhttps://joshbruce.com/published-sub xml ); -})->group('sitemap', 'focus'); +})->group('request', 'response', 'sitemap'); diff --git a/tests/TestFileSystem.php b/tests/TestFileSystem.php new file mode 100644 index 00000000..1bf6effc --- /dev/null +++ b/tests/TestFileSystem.php @@ -0,0 +1,17 @@ + a{ + position:var(--relative); + text-align:var(--center); + font-size:var(--s-font); + display:var(--block); + margin:var(--l-spacer); + font-weight:var(--light-font); + padding:var(--m-spacer); +} +body > a[id=content-top]:before{ + display:var(--inline-block); + content:var(--icon-decrease); + position:var(--absolute); + left:var(--0-spacer); +} +body > a[id=content-top]:after{ + display:var(--inline-block); + content:var(--icon-decrease); + position:var(--absolute); + right:var(--0-spacer); +} +body > a[id=go-to-top]:before{ + display:var(--inline-block); + content:var(--icon-increase); + position:var(--absolute); + left:var(--0-spacer); +} +body > a[id=go-to-top]:after{ + display:var(--inline-block); + content:var(--icon-increase); + position:var(--absolute); + right:var(--0-spacer); +} + +a:hover{ + cursor:var(--pointer); +} + +a{ + font-weight:var(--bold-font); + -webkit-text-decoration:var(--none); + text-decoration:var(--none); + color:var(--key); + transition:var(--transition-color); +} +a:hover{ + color:var(--gray-darker); +} + +[is=dateblock]{ + margin-bottom:var(--l-spacer); +} +[is=dateblock] p{ + margin:var(--0-spacer); + font-size:var(--xs-font); +} +[is=dateblock] p > time{ + font-weight:var(--bold-font); +} + +h1, +h2, +h3, +h4, +h5, +h6{ + margin-top:var(--l-spacer); + margin-bottom:var(--xs-spacer); + font-family:var(--head-font); + font-size:clamp(30pt, 9.375vw, 35pt); + line-height:var(--l-line-height); +} +h1 + ol, h1 + ul, h1 + p, +h2 + ol, +h2 + ul, +h2 + p, +h3 + ol, +h3 + ul, +h3 + p, +h4 + ol, +h4 + ul, +h4 + p, +h5 + ol, +h5 + ul, +h5 + p, +h6 + ol, +h6 + ul, +h6 + p{ + margin-top:var(--0-spacer); + padding-top:var(--0-spacer); +} + +h1{ + margin-top:var(--m-spacer); +} + +h2{ + font-size:var(--xl-font); +} + +h3, +h4, +h5, +h6{ + font-size:var(--l-font); + text-align:var(--center); +} + +hr{ + width:33%; + border:var(--2-px) var(--solid) var(--gray-darkest); + border-radius:var(--1-px); +} + +code{ + font-family:var(--code-font); + font-size:var(--s-font); +} + +.toc{ + padding:var(--none); + list-style:var(--none); +} +.toc li{ + border-bottom:var(--1-px) var(--solid) var(--gray-darker); +} +.toc li:last-child{ + border-bottom:var(--none); +} +.toc h3{ + margin:var(--xs-spacer); + text-align:var(--left); + font-size:var(--l-font); +} +.toc h3 a{ + display:var(--block); + padding:var(--m-spacer); +} +.toc h3 small{ + font-size:var(--s-font); + display:var(--block); + color:var(--gray-darker); + font-weight:var(--light-font); +} + +main, article{ + padding:var(--0-spacer) var(--m-spacer); + padding-top:var(--m-spacer); +} +main img, +main iframe[src*="https://jsfiddle.net"], article img, +article iframe[src*="https://jsfiddle.net"]{ + display:var(--block); + margin:var(--margin-centered); + width:80%; + border:var(--3-px) var(--solid) var(--gray-darker); +} +main iframe[src*="https://jsfiddle.net"], article iframe[src*="https://jsfiddle.net"]{ + min-height:300px; +} +main abbr, article abbr{ + -webkit-text-decoration:var(--none); + text-decoration:var(--none); + border-bottom:var(--1-px) var(--dashed) var(--gray-darker); +} +main dl > dt, article dl > dt{ + margin-top:var(--m-spacer); +} +main dl > dd, article dl > dd{ + margin-bottom:var(--s-spacer); +} +main details, article details{ + border:var(--1-px) var(--solid) var(--key); + border-radius:var(--3-px); + margin-top:var(--m-spacer); + padding:0.5em 0.5em 0; + font-weight:var(--bold-font); +} +main details summary, article details summary{ + color:var(--key); + cursor:var(--pointer); + margin:-0.5em -0.5em 0; + padding:0.5em; +} + +nav > ul{ + margin:var(--0-spacer); + padding:var(--0-spacer); + list-style:var(--none); +} +nav > ul > li:first-of-type{ + margin-bottom:var(--m-spacer); +} +nav > ul ul{ + list-style:var(--none); + margin:var(--0-spacer) var(--0-spacer); + padding:var(--0-spacer) var(--0-spacer); +} +nav > ul ul a{ + font-size:var(--s-font); +} +nav > ul ul li:last-of-type a{ + padding-bottom:var(--s-spacer); +} +nav > ul a{ + display:var(--block); + overflow:var(--hidden); + text-align:var(--center); + position:var(--relative); +} +nav > ul abbr{ + border-color:var(--gray-lightest); +} + +@media (prefers-reduced-motion: no-preference){ + html{ + scroll-behavior:var(--smooth); + } + + nav > ul a:after{ + content:""; + position:var(--absolute); + width:25%; + border-top:var(--1-px) var(--solid) var(--gray-darker); + opacity:0.25; + top:60%; + left:-100%; + transition-delay:all 0.75s; + transition:all 0.75s ease-out; + } + nav > ul a:hover:after{ + left:100%; + } +} +@media (prefers-color-scheme: dark){ + body{ + background:var(--gray-darkest); + color:var(--gray-lightest); + font-weight:var(--medium-font); + } + + hr{ + border:1px solid var(--gray-lightest); + } + + a{ + color:var(--key-light); + font-weight:var(--x-bold-font); + } + a:hover{ + color:var(--gray-lightest); + } + + [is=dateblock] p > time, b, strong{ + font-weight:var(--x-bold-font); + } + + nav > ul a:after{ + border-top:var(--1-px) var(--solid) var(--gray-lightest); + } + + main img, +main iframe[src*="https://jsfiddle.net"], article img, +article iframe[src*="https://jsfiddle.net"]{ + border:3px solid var(--gray-lightest); + } + main details, article details{ + font-weight:var(--bold-font); + } + main details summary, article details summary{ + color:var(--key-light); + } +} +blockquote{ + margin:var(--m-spacer); + font-style:var(--italic); +} + +.heading-permalink{ + display:var(--inline-block); + margin-right:var(--2xs-spacer); +} + +a[rel~=noreferrer]:after{ + content:var(--icon-new-window); + display:var(--inline-block); + margin-left:var(--2xs-spacer); + position:var(--relative); + top:var(--icon-lift-spacer); + font-size:var(--xs-font); +} + +footer{ + margin-top:var(--l-spacer); +} +footer > p{ + text-align:var(--center); + font-size:var(--xs-font); +} \ No newline at end of file diff --git a/tests/test-content/content/public/assets/css/main.min.css b/tests/test-content/content/public/assets/css/main.min.css new file mode 100644 index 00000000..be9e0d49 --- /dev/null +++ b/tests/test-content/content/public/assets/css/main.min.css @@ -0,0 +1,2 @@ +@charset "UTF-8";:root{--header-font:"Gill Sans", "Gill Sans MT", Calibri, sans-serif;--body-font:"Open Sans", "Helvetica Neue", Verdana, sans-serif;--code-font:Consolas, monaco, monospace;--x-bold-font:600;--bold-font:500;--medium-font:300;--light-font:250;--2xl-font:clamp(30pt, 9.375vw, 35pt);--xl-font:clamp(25pt, 7.8125vw, 30pt);--l-font:clamp(20pt, 6.25vw, 25pt);--m-font:clamp(16pt, 5vw, 18pt);--s-font:clamp(13pt, 4.0625vw, 15pt);--xs-font:clamp(10pt, 3.125vw, 14pt);--l-line-height:calc(var(--2xl-font) * 1);--m-line-height:calc(var(--s-font) * 1.62);--s-line-height:calc(var(--s-font) * 1.25);--line-length:70ch;--key:#0A6276;--key-light:#5FCAF2;--gray-darkest:#030303;--gray-darker:#0F2124;--gray-lightest:#FCFDFD;--vw-0:0vw;--0-spacer:0;--xl-spacer:4rem;--l-spacer:2rem;--m-spacer:1rem;--s-spacer:0.75rem;--xs-spacer:0.5rem;--2xs-spacer:0.25rem;--3xs-spacer:0.1rem;--1-px:1px;--2-px:2px;--3-px:3px;--400-px:400px;--icon-new-window:"⧉";--icon-increase:"◭";--icon-decrease:"⧩";--icon-hold:"≅";--icon-lift-spacer:-0.5rem;--margin-centered:var(--0-spacer) var(--auto);--smooth:smooth;--auto:auto;--pointer:pointer;--none:none;--transition-color:color 0.25s;--center:center;--left:left;--inline-block:inline-block;--solid:solid;--dashed:dashed;--transparent:transparent;--relative:relative;--absolute:absolute;--italic:italic;--block:block;--both:both;--hidden:hidden}body{min-height:var(--vw-0);max-width:var(--line-length);margin:var(--margin-centered);background:var(--gray-lightest);color:var(--gray-darkest);font-family:var(--body-font);font-size:var(--m-font);line-height:var(--m-line-height)}body>a,code{font-size:var(--s-font)}body,body>a{font-weight:var(--light-font)}body>a{position:var(--relative);text-align:var(--center);display:var(--block);margin:var(--l-spacer);padding:var(--m-spacer)}body>a[id=content-top]:after,body>a[id=content-top]:before{display:var(--inline-block);content:var(--icon-decrease);position:var(--absolute)}body>a[id=content-top]:before{left:var(--0-spacer)}body>a[id=content-top]:after{right:var(--0-spacer)}body>a[id=go-to-top]:after,body>a[id=go-to-top]:before{display:var(--inline-block);content:var(--icon-increase);position:var(--absolute)}body>a[id=go-to-top]:before{left:var(--0-spacer)}body>a[id=go-to-top]:after{right:var(--0-spacer)}a:hover{cursor:var(--pointer);color:var(--gray-darker)}a{-webkit-text-decoration:var(--none);text-decoration:var(--none);color:var(--key);transition:var(--transition-color)}[is=dateblock]{margin-bottom:var(--l-spacer)}[is=dateblock] p{margin:var(--0-spacer);font-size:var(--xs-font)}[is=dateblock] p>time,a{font-weight:var(--bold-font)}h1,h2,h3,h4,h5,h6{margin-bottom:var(--xs-spacer);font-family:var(--head-font);font-size:clamp(30pt,9.375vw,35pt);line-height:var(--l-line-height)}h2,h3,h4,h5,h6{margin-top:var(--l-spacer)}h1+ol,h1+p,h1+ul,h2+ol,h2+p,h2+ul,h3+ol,h3+p,h3+ul,h4+ol,h4+p,h4+ul,h5+ol,h5+p,h5+ul,h6+ol,h6+p,h6+ul{margin-top:var(--0-spacer);padding-top:var(--0-spacer)}article dl>dt,h1,main dl>dt{margin-top:var(--m-spacer)}h2{font-size:var(--xl-font)}h3,h4,h5,h6{font-size:var(--l-font);text-align:var(--center)}hr{width:33%;border:var(--2-px) var(--solid) var(--gray-darkest);border-radius:var(--1-px)}code{font-family:var(--code-font)}.toc{padding:var(--none);list-style:var(--none)}.toc li{border-bottom:var(--1-px) var(--solid) var(--gray-darker)}.toc li:last-child{border-bottom:var(--none)}.toc h3{margin:var(--xs-spacer);text-align:var(--left);font-size:var(--l-font)}.toc h3 a{display:var(--block);padding:var(--m-spacer)}.toc h3 small{font-size:var(--s-font);display:var(--block);color:var(--gray-darker);font-weight:var(--light-font)}article,main{padding:var(--0-spacer) var(--m-spacer);padding-top:var(--m-spacer)}article iframe[src*="https://jsfiddle.net"],article img,main iframe[src*="https://jsfiddle.net"],main img{display:var(--block);margin:var(--margin-centered);width:80%;border:var(--3-px) var(--solid) var(--gray-darker)}article iframe[src*="https://jsfiddle.net"],main iframe[src*="https://jsfiddle.net"]{min-height:300px}article abbr,main abbr{-webkit-text-decoration:var(--none);text-decoration:var(--none);border-bottom:var(--1-px) var(--dashed) var(--gray-darker)}article dl>dd,main dl>dd{margin-bottom:var(--s-spacer)}article details,main details{border:var(--1-px) var(--solid) var(--key);border-radius:var(--3-px);margin-top:var(--m-spacer);padding:.5em .5em 0;font-weight:var(--bold-font)}article details summary,main details summary{color:var(--key);cursor:var(--pointer);margin:-.5em -.5em 0;padding:.5em}nav>ul{margin:var(--0-spacer);padding:var(--0-spacer);list-style:var(--none)}nav>ul>li:first-of-type{margin-bottom:var(--m-spacer)}nav>ul ul{list-style:var(--none);margin:var(--0-spacer) var(--0-spacer);padding:var(--0-spacer) var(--0-spacer)}nav>ul ul a{font-size:var(--s-font)}nav>ul ul li:last-of-type a{padding-bottom:var(--s-spacer)}footer>p,nav>ul a{text-align:var(--center)}nav>ul a{display:var(--block);overflow:var(--hidden);position:var(--relative)}nav>ul abbr{border-color:var(--gray-lightest)}@media (prefers-reduced-motion:no-preference){html{scroll-behavior:var(--smooth)}nav>ul a:after{content:"";position:var(--absolute);width:25%;border-top:var(--1-px) var(--solid) var(--gray-darker);opacity:.25;top:60%;left:-100%;transition-delay:all .75s;transition:all .75s ease-out}nav>ul a:hover:after{left:100%}}@media (prefers-color-scheme:dark){body{background:var(--gray-darkest);font-weight:var(--medium-font)}hr{border:1px solid var(--gray-lightest)}a:hover,body{color:var(--gray-lightest)}[is=dateblock] p>time,a,b,strong{font-weight:var(--x-bold-font)}nav>ul a:after{border-top:var(--1-px) var(--solid) var(--gray-lightest)}article iframe[src*="https://jsfiddle.net"],article img,main iframe[src*="https://jsfiddle.net"],main img{border:3px solid var(--gray-lightest)}article details,main details{font-weight:var(--bold-font)}a,article details summary,main details summary{color:var(--key-light)}}blockquote{margin:var(--m-spacer);font-style:var(--italic)}.heading-permalink{display:var(--inline-block);margin-right:var(--2xs-spacer)}a[rel~=noreferrer]:after{content:var(--icon-new-window);display:var(--inline-block);margin-left:var(--2xs-spacer);position:var(--relative);top:var(--icon-lift-spacer);font-size:var(--xs-font)}footer{margin-top:var(--l-spacer)}footer>p{font-size:var(--xs-font)} +/*# sourceMappingURL=main.min.css.map */ diff --git a/tests/test-content/content/public/assets/css/main.min.css.map b/tests/test-content/content/public/assets/css/main.min.css.map new file mode 100644 index 00000000..6dbb27c9 --- /dev/null +++ b/tests/test-content/content/public/assets/css/main.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["main.css","../../../main.scss",""],"names":[],"mappings":"AAAA,gBAAgB,CCAhB,MACE,8DAAA,CACA,8DAAA,CACA,uCAAA,CAEA,iBAAA,CACA,eAAA,CACA,iBAAA,CACA,gBAAA,CAEA,qCAAA,CACA,qCAAA,CACA,kCAAA,CACA,+BAAA,CACA,oCAAA,CACA,oCAAA,CAEA,yCAAA,CACA,0CAAA,CACA,0CAAA,CAEA,kBAAA,CAEA,aAAA,CACA,mBAAA,CACA,sBAAA,CACA,qBAAA,CACA,uBAAA,CAEA,UAAA,CAEA,YAAA,CACA,gBAAA,CACA,eAAA,CACA,eAAA,CACA,kBAAA,CACA,kBAAA,CACA,oBAAA,CACA,mBAAA,CACA,UAAA,CACA,UAAA,CACA,UAAA,CACA,cAAA,CAEA,qBAAA,CACA,mBAAA,CACA,mBAAA,CACA,eAAA,CACA,0BAAA,CAEA,6CAAA,CAEA,eAAA,CACA,WAAA,CACA,iBAAA,CACA,WAAA,CACA,8BAAA,CACA,eAAA,CACA,WAAA,CACA,2BAAA,CACA,aAAA,CACA,eAAA,CACA,yBAAA,CACA,mBAAA,CACA,mBAAA,CACA,eAAA,CACA,aAAA,CACA,WAAA,CACA,eDRF,CCWA,KACE,sBAAA,CACA,4BAAA,CACA,6BAAA,CACA,+BAAA,CACA,yBAAA,CACA,4BAAA,CACA,uBAAA,CACA,gCDPF,CExEA,YDoME,sBAAA,ECpMF,YDwFI,4BAAA,EANF,AClFF,ODmFI,wBAAA,CACA,wBAAA,CAEA,oBAAA,CACA,sBAAA,CAEA,uBDRJ,CEjFA,2DDoGQ,2BAAA,CACA,4BAAA,CACA,uBAAA,EAVF,AC5FN,8BDgGQ,oBDTR,CCYM,6BAIE,qBDVR,CE7FA,uDDoHQ,2BAAA,CACA,4BAAA,CACA,uBAAA,EAVF,AC5GN,4BDgHQ,oBDbR,CCgBM,2BAIE,qBDdR,CCoBA,QACE,qBAAA,CAUE,uBAAA,CD3BJ,CCoBA,EAEE,mCAAA,CAAA,2BAAA,CACA,gBAAA,CACA,kCDjBF,CCwBA,eACE,6BDlBF,CCoBE,iBACE,sBAAA,CACA,wBDlBJ,CCoBI,wBACE,4BDlBN,CCuBA,kBAOE,8BAAA,CACA,4BAAA,CACA,kCAAA,CAEA,gCDrBF,CCUA,eAME,yBAAA,CDhBF,CCuBE,sGACE,0BAAA,CACA,2BDNJ,CCUA,4BACE,0BDPF,CCUA,GACE,wBDPF,CCUA,YAIE,uBAAA,CACA,wBDPF,CCUA,GACE,SAAA,CACA,mDAAA,CACA,yBDPF,CCUA,KACE,4BDNF,CCUA,KACE,mBAAA,CACA,sBDPF,CCSE,QACE,yDDPJ,CCSI,mBACE,yBDPN,CCWE,QACE,uBAAA,CACA,sBAAA,CACA,uBDTJ,CCWI,UACE,oBAAA,CACA,uBDTN,CCYI,cACE,uBAAA,CACA,oBAAA,CACA,wBAAA,CACA,6BDVN,CCeA,aACE,uCAAA,CACA,2BDZF,CCcE,0GAEE,oBAAA,CACA,6BAAA,CACA,SAAA,CACA,kDDXJ,CCcE,qFACE,gBDZJ,CCeE,uBACE,mCAAA,CAAA,2BAAA,CACA,0DDbJ,CCqBI,yBACE,6BDhBN,CCoBE,6BACE,0CAAA,CACA,yBAAA,CACA,0BAAA,CACA,mBAAA,CACA,4BDlBJ,CCoBI,6CACE,gBAAA,CACA,qBAAA,CACA,oBAAA,CACA,YDlBN,CCuBA,OACE,sBAAA,CACA,uBAAA,CACA,sBDpBF,CCsBE,wBACE,6BDpBJ,CCuBE,UACE,sBAAA,CACA,sCAAA,CACA,uCDrBJ,CCsBI,YACE,uBDpBN,CCuBI,4BACE,8BDrBN,CElRA,kBDkZI,uBAAA,EAvGF,AC3SF,SD4SI,oBAAA,CACA,sBAAA,CAEA,wBDvBJ,CC0BE,YACE,iCDxBJ,CC6BA,8CACE,KACE,6BD1BF,CC8BE,eACE,UAAA,CACA,wBAAA,CACA,SAAA,CACA,sDAAA,CACA,WAAA,CACA,OAAA,CACA,UAAA,CACA,yBAAA,CACA,4BD3BJ,CC8BE,qBACG,SD5BL,CACF,CCgCA,mCACE,KACE,8BAAA,CAEA,8BD9BF,CCiCA,GACE,qCD9BF,CCqCE,aACE,0BD9BJ,CCkCA,iCACM,8BD/BN,CCkCA,eACE,wDD/BF,CCmCE,0GAEE,qCD/BJ,CCkCE,6BACE,4BDhCJ,CCkCI,+CACE,sBDhCN,CACF,CCqCA,WACE,sBAAA,CACA,wBDnCF,CCsCA,mBACE,2BAAA,CACA,8BDnCF,CCsCA,yBACE,8BAAA,CACA,2BAAA,CACA,6BAAA,CACA,wBAAA,CACA,2BAAA,CACA,wBDnCF,CCsCA,OACE,0BDnCF,CCoCE,SAEE,wBDlCJ","file":"main.min.css","sourcesContent":["@charset \"UTF-8\";\n:root {\n --header-font: \"Gill Sans\", \"Gill Sans MT\", Calibri, sans-serif;\n --body-font: \"Open Sans\", \"Helvetica Neue\", Verdana, sans-serif;\n --code-font: Consolas, monaco, monospace;\n --x-bold-font: 600;\n --bold-font: 500;\n --medium-font: 300;\n --light-font: 250;\n --2xl-font: clamp(30pt, 9.375vw, 35pt);\n --xl-font: clamp(25pt, 7.8125vw, 30pt);\n --l-font: clamp(20pt, 6.25vw, 25pt);\n --m-font: clamp(16pt, 5vw, 18pt);\n --s-font: clamp(13pt, 4.0625vw, 15pt);\n --xs-font: clamp(10pt, 3.125vw, 14pt);\n --l-line-height: calc(var(--2xl-font) * 1);\n --m-line-height: calc(var(--s-font) * 1.62);\n --s-line-height: calc(var(--s-font) * 1.25);\n --line-length: 70ch;\n --key: #0A6276;\n --key-light: #5FCAF2;\n --gray-darkest: #030303;\n --gray-darker: #0F2124;\n --gray-lightest: #FCFDFD;\n --vw-0: 0vw;\n --0-spacer: 0;\n --xl-spacer: 4rem;\n --l-spacer: 2rem;\n --m-spacer: 1rem;\n --s-spacer: 0.75rem;\n --xs-spacer: 0.5rem;\n --2xs-spacer: 0.25rem;\n --3xs-spacer: 0.1rem;\n --1-px: 1px;\n --2-px: 2px;\n --3-px: 3px;\n --400-px: 400px;\n --icon-new-window: \"⧉\";\n --icon-increase: \"◭\";\n --icon-decrease: \"⧩\";\n --icon-hold: \"≅\";\n --icon-lift-spacer: -0.5rem;\n --margin-centered: var(--0-spacer) var(--auto);\n --smooth: smooth;\n --auto: auto;\n --pointer: pointer;\n --none: none;\n --transition-color: color 0.25s;\n --center: center;\n --left: left;\n --inline-block: inline-block;\n --solid: solid;\n --dashed: dashed;\n --transparent: transparent;\n --relative: relative;\n --absolute: absolute;\n --italic: italic;\n --block: block;\n --both: both;\n --hidden: hidden;\n}\n\nbody {\n min-height: var(--vw-0);\n max-width: var(--line-length);\n margin: var(--margin-centered);\n background: var(--gray-lightest);\n color: var(--gray-darkest);\n font-family: var(--body-font);\n font-size: var(--m-font);\n line-height: var(--m-line-height);\n font-weight: var(--light-font);\n}\nbody > a {\n position: var(--relative);\n text-align: var(--center);\n font-size: var(--s-font);\n display: var(--block);\n margin: var(--l-spacer);\n font-weight: var(--light-font);\n padding: var(--m-spacer);\n}\nbody > a[id=content-top]:before {\n display: var(--inline-block);\n content: var(--icon-decrease);\n position: var(--absolute);\n left: var(--0-spacer);\n}\nbody > a[id=content-top]:after {\n display: var(--inline-block);\n content: var(--icon-decrease);\n position: var(--absolute);\n right: var(--0-spacer);\n}\nbody > a[id=go-to-top]:before {\n display: var(--inline-block);\n content: var(--icon-increase);\n position: var(--absolute);\n left: var(--0-spacer);\n}\nbody > a[id=go-to-top]:after {\n display: var(--inline-block);\n content: var(--icon-increase);\n position: var(--absolute);\n right: var(--0-spacer);\n}\n\na:hover {\n cursor: var(--pointer);\n}\n\na {\n font-weight: var(--bold-font);\n text-decoration: var(--none);\n color: var(--key);\n transition: var(--transition-color);\n}\na:hover {\n color: var(--gray-darker);\n}\n\n[is=dateblock] {\n margin-bottom: var(--l-spacer);\n}\n[is=dateblock] p {\n margin: var(--0-spacer);\n font-size: var(--xs-font);\n}\n[is=dateblock] p > time {\n font-weight: var(--bold-font);\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n margin-top: var(--l-spacer);\n margin-bottom: var(--xs-spacer);\n font-family: var(--head-font);\n font-size: clamp(30pt, 9.375vw, 35pt);\n line-height: var(--l-line-height);\n}\nh1 + ol, h1 + ul, h1 + p,\nh2 + ol,\nh2 + ul,\nh2 + p,\nh3 + ol,\nh3 + ul,\nh3 + p,\nh4 + ol,\nh4 + ul,\nh4 + p,\nh5 + ol,\nh5 + ul,\nh5 + p,\nh6 + ol,\nh6 + ul,\nh6 + p {\n margin-top: var(--0-spacer);\n padding-top: var(--0-spacer);\n}\n\nh1 {\n margin-top: var(--m-spacer);\n}\n\nh2 {\n font-size: var(--xl-font);\n}\n\nh3,\nh4,\nh5,\nh6 {\n font-size: var(--l-font);\n text-align: var(--center);\n}\n\nhr {\n width: 33%;\n border: var(--2-px) var(--solid) var(--gray-darkest);\n border-radius: var(--1-px);\n}\n\ncode {\n font-family: var(--code-font);\n font-size: var(--s-font);\n}\n\n.toc {\n padding: var(--none);\n list-style: var(--none);\n}\n.toc li {\n border-bottom: var(--1-px) var(--solid) var(--gray-darker);\n}\n.toc li:last-child {\n border-bottom: var(--none);\n}\n.toc h3 {\n margin: var(--xs-spacer);\n text-align: var(--left);\n font-size: var(--l-font);\n}\n.toc h3 a {\n display: var(--block);\n padding: var(--m-spacer);\n}\n.toc h3 small {\n font-size: var(--s-font);\n display: var(--block);\n color: var(--gray-darker);\n font-weight: var(--light-font);\n}\n\nmain, article {\n padding: var(--0-spacer) var(--m-spacer);\n padding-top: var(--m-spacer);\n}\nmain img,\nmain iframe[src*=\"https://jsfiddle.net\"], article img,\narticle iframe[src*=\"https://jsfiddle.net\"] {\n display: var(--block);\n margin: var(--margin-centered);\n width: 80%;\n border: var(--3-px) var(--solid) var(--gray-darker);\n}\nmain iframe[src*=\"https://jsfiddle.net\"], article iframe[src*=\"https://jsfiddle.net\"] {\n min-height: 300px;\n}\nmain abbr, article abbr {\n text-decoration: var(--none);\n border-bottom: var(--1-px) var(--dashed) var(--gray-darker);\n}\nmain dl > dt, article dl > dt {\n margin-top: var(--m-spacer);\n}\nmain dl > dd, article dl > dd {\n margin-bottom: var(--s-spacer);\n}\nmain details, article details {\n border: var(--1-px) var(--solid) var(--key);\n border-radius: var(--3-px);\n margin-top: var(--m-spacer);\n padding: 0.5em 0.5em 0;\n font-weight: var(--bold-font);\n}\nmain details summary, article details summary {\n color: var(--key);\n cursor: var(--pointer);\n margin: -0.5em -0.5em 0;\n padding: 0.5em;\n}\n\nnav > ul {\n margin: var(--0-spacer);\n padding: var(--0-spacer);\n list-style: var(--none);\n}\nnav > ul > li:first-of-type {\n margin-bottom: var(--m-spacer);\n}\nnav > ul ul {\n list-style: var(--none);\n margin: var(--0-spacer) var(--0-spacer);\n padding: var(--0-spacer) var(--0-spacer);\n}\nnav > ul ul a {\n font-size: var(--s-font);\n}\nnav > ul ul li:last-of-type a {\n padding-bottom: var(--s-spacer);\n}\nnav > ul a {\n display: var(--block);\n overflow: var(--hidden);\n text-align: var(--center);\n position: var(--relative);\n}\nnav > ul abbr {\n border-color: var(--gray-lightest);\n}\n\n@media (prefers-reduced-motion: no-preference) {\n html {\n scroll-behavior: var(--smooth);\n }\n\n nav > ul a:after {\n content: \"\";\n position: var(--absolute);\n width: 25%;\n border-top: var(--1-px) var(--solid) var(--gray-darker);\n opacity: 0.25;\n top: 60%;\n left: -100%;\n transition-delay: all 0.75s;\n transition: all 0.75s ease-out;\n }\n nav > ul a:hover:after {\n left: 100%;\n }\n}\n@media (prefers-color-scheme: dark) {\n body {\n background: var(--gray-darkest);\n color: var(--gray-lightest);\n font-weight: var(--medium-font);\n }\n\n hr {\n border: 1px solid var(--gray-lightest);\n }\n\n a {\n color: var(--key-light);\n font-weight: var(--x-bold-font);\n }\n a:hover {\n color: var(--gray-lightest);\n }\n\n [is=dateblock] p > time, b, strong {\n font-weight: var(--x-bold-font);\n }\n\n nav > ul a:after {\n border-top: var(--1-px) var(--solid) var(--gray-lightest);\n }\n\n main img,\nmain iframe[src*=\"https://jsfiddle.net\"], article img,\narticle iframe[src*=\"https://jsfiddle.net\"] {\n border: 3px solid var(--gray-lightest);\n }\n main details, article details {\n font-weight: var(--bold-font);\n }\n main details summary, article details summary {\n color: var(--key-light);\n }\n}\nblockquote {\n margin: var(--m-spacer);\n font-style: var(--italic);\n}\n\n.heading-permalink {\n display: var(--inline-block);\n margin-right: var(--2xs-spacer);\n}\n\na[rel~=noreferrer]:after {\n content: var(--icon-new-window);\n display: var(--inline-block);\n margin-left: var(--2xs-spacer);\n position: var(--relative);\n top: var(--icon-lift-spacer);\n font-size: var(--xs-font);\n}\n\nfooter {\n margin-top: var(--l-spacer);\n}\nfooter > p {\n text-align: var(--center);\n font-size: var(--xs-font);\n}\n\n/*# sourceMappingURL=file:///Users/alex/Public/joshbruce-repos/site-joshbruce.com/content/assets/sass/main.scss */",null,null]} \ No newline at end of file diff --git a/tests/test-content/content/public/assets/favicons/android-chrome-192x192.png b/tests/test-content/content/public/assets/favicons/android-chrome-192x192.png new file mode 100644 index 00000000..b0cf6262 Binary files /dev/null and b/tests/test-content/content/public/assets/favicons/android-chrome-192x192.png differ diff --git a/tests/test-content/content/public/assets/favicons/android-chrome-512x512.png b/tests/test-content/content/public/assets/favicons/android-chrome-512x512.png new file mode 100644 index 00000000..59fcfd54 Binary files /dev/null and b/tests/test-content/content/public/assets/favicons/android-chrome-512x512.png differ diff --git a/tests/test-content/content/public/assets/favicons/apple-touch-icon.png b/tests/test-content/content/public/assets/favicons/apple-touch-icon.png new file mode 100644 index 00000000..74f72ee9 Binary files /dev/null and b/tests/test-content/content/public/assets/favicons/apple-touch-icon.png differ diff --git a/tests/test-content/content/public/assets/favicons/favicon-16x16.png b/tests/test-content/content/public/assets/favicons/favicon-16x16.png new file mode 100644 index 00000000..26c84693 Binary files /dev/null and b/tests/test-content/content/public/assets/favicons/favicon-16x16.png differ diff --git a/tests/test-content/content/public/assets/favicons/favicon-32x32.png b/tests/test-content/content/public/assets/favicons/favicon-32x32.png new file mode 100644 index 00000000..bbb0aba4 Binary files /dev/null and b/tests/test-content/content/public/assets/favicons/favicon-32x32.png differ diff --git a/tests/test-content/content/public/assets/favicons/favicon.ico b/tests/test-content/content/public/assets/favicons/favicon.ico new file mode 100644 index 00000000..7a22a602 Binary files /dev/null and b/tests/test-content/content/public/assets/favicons/favicon.ico differ diff --git a/tests/test-content/content/public/assets/favicons/site.webmanifest b/tests/test-content/content/public/assets/favicons/site.webmanifest new file mode 100644 index 00000000..45dc8a20 --- /dev/null +++ b/tests/test-content/content/public/assets/favicons/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/tests/test-content/content/public/content.md b/tests/test-content/content/public/content.md new file mode 100644 index 00000000..8866c764 --- /dev/null +++ b/tests/test-content/content/public/content.md @@ -0,0 +1,3 @@ +--- +title: Test content root +--- diff --git a/tests/test-content/content/public/error-404.md b/tests/test-content/content/public/error-404.md new file mode 100644 index 00000000..df5025b0 --- /dev/null +++ b/tests/test-content/content/public/error-404.md @@ -0,0 +1,9 @@ +--- +title: Page not found +--- + +# Oh, no + +I'm sorry, it appears the content you are looking for doesn't exist. + +It's either been moved, deleted, never existed, or some combination. diff --git a/tests/test-content/content/public/published-folder-with-draft-content/_content.md b/tests/test-content/content/public/published-folder-with-draft-content/_content.md new file mode 100644 index 00000000..e69de29b diff --git a/tests/test-content/content/public/published-sub/content.md b/tests/test-content/content/public/published-sub/content.md new file mode 100644 index 00000000..ef1a82a7 --- /dev/null +++ b/tests/test-content/content/public/published-sub/content.md @@ -0,0 +1,3 @@ +--- +title: Sub-folder content title +--- diff --git a/tests/test-content/content/public/sitemap.xml b/tests/test-content/content/public/sitemap.xml new file mode 100644 index 00000000..87ba159a --- /dev/null +++ b/tests/test-content/content/public/sitemap.xml @@ -0,0 +1,3 @@ +--- +template: 'sitemap' +---