From 98a23cab6e903232f76c09e55cd8c4386284e008 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 19 Dec 2024 20:13:47 +0000 Subject: [PATCH] Merge pull request #2064 from hydephp/replace-glob-brace [1.x] Replace `GLOB_BRACE` with a more robust solution https://github.com/hydephp/develop/commit/0d1280c0dd38f54962dc64c4275abaef071d8d4b --- src/Facades/Filesystem.php | 15 + src/Foundation/Kernel/FileCollection.php | 2 +- src/Foundation/Kernel/Filesystem.php | 13 + src/Framework/Actions/Internal/FileFinder.php | 66 +++++ .../PreBuildTasks/CleanSiteDirectory.php | 3 +- src/Support/DataCollections.php | 7 +- src/Support/Filesystem/MediaFile.php | 17 +- tests/Feature/DiscoveryServiceTest.php | 2 +- tests/Feature/FileCollectionTest.php | 16 + tests/Feature/Foundation/FilesystemTest.php | 82 ++++++ tests/Unit/DataCollectionUnitTest.php | 45 ++- tests/Unit/FileFinderTest.php | 276 ++++++++++++++++++ .../Pages/PageModelGetAllFilesHelperTest.php | 88 ------ .../Pages/PageModelGetFileHelpersTest.php | 115 ++++++++ tests/Unit/Pages/PageModelGetHelperTest.php | 97 ------ 15 files changed, 615 insertions(+), 229 deletions(-) create mode 100644 src/Framework/Actions/Internal/FileFinder.php create mode 100644 tests/Unit/FileFinderTest.php delete mode 100644 tests/Unit/Pages/PageModelGetAllFilesHelperTest.php create mode 100644 tests/Unit/Pages/PageModelGetFileHelpersTest.php delete mode 100644 tests/Unit/Pages/PageModelGetHelperTest.php diff --git a/src/Facades/Filesystem.php b/src/Facades/Filesystem.php index fa043421..2cbcf58b 100644 --- a/src/Facades/Filesystem.php +++ b/src/Facades/Filesystem.php @@ -69,6 +69,21 @@ public static function smartGlob(string $pattern, int $flags = 0): Collection return self::kernel()->filesystem()->smartGlob($pattern, $flags); } + /** + * Find files in the project's directory, with optional filtering by extension and recursion. + * + * The returned collection will be a list of paths relative to the project root. + * + * @param string $directory + * @param string|array|false $matchExtensions The file extension(s) to match, or false to match all files. + * @param bool $recursive Whether to search recursively or not. + * @return \Illuminate\Support\Collection + */ + public static function findFiles(string $directory, string|array|false $matchExtensions = false, bool $recursive = false): Collection + { + return self::kernel()->filesystem()->findFiles($directory, $matchExtensions, $recursive); + } + /** * Touch one or more files in the project's directory. * diff --git a/src/Foundation/Kernel/FileCollection.php b/src/Foundation/Kernel/FileCollection.php index b52355dd..49c67e49 100644 --- a/src/Foundation/Kernel/FileCollection.php +++ b/src/Foundation/Kernel/FileCollection.php @@ -59,7 +59,7 @@ protected function runExtensionHandlers(): void protected function discoverFilesFor(string $pageClass): void { // Scan the source directory, and directories therein, for files that match the model's file extension. - foreach (Filesystem::smartGlob($pageClass::sourcePath('{*,**/*}'), GLOB_BRACE) as $path) { + foreach (Filesystem::findFiles($pageClass::sourceDirectory(), $pageClass::fileExtension(), true) as $path) { if (! str_starts_with(basename((string) $path), '_')) { $this->addFile(SourceFile::make($path, $pageClass)); } diff --git a/src/Foundation/Kernel/Filesystem.php b/src/Foundation/Kernel/Filesystem.php index a8f36a32..c4532c40 100644 --- a/src/Foundation/Kernel/Filesystem.php +++ b/src/Foundation/Kernel/Filesystem.php @@ -8,6 +8,7 @@ use Hyde\Foundation\HydeKernel; use Hyde\Foundation\PharSupport; use Illuminate\Support\Collection; +use Hyde\Framework\Actions\Internal\FileFinder; use function collect; use function Hyde\normalize_slashes; @@ -183,4 +184,16 @@ public function smartGlob(string $pattern, int $flags = 0): Collection return $files->map(fn (string $path): string => $this->pathToRelative($path)); } + + /** + * @param string|array|false $matchExtensions + * @return \Illuminate\Support\Collection + */ + public function findFiles(string $directory, string|array|false $matchExtensions = false, bool $recursive = false): Collection + { + /** @var \Hyde\Framework\Actions\Internal\FileFinder $finder */ + $finder = app(FileFinder::class); + + return $finder->handle($directory, $matchExtensions, $recursive); + } } diff --git a/src/Framework/Actions/Internal/FileFinder.php b/src/Framework/Actions/Internal/FileFinder.php new file mode 100644 index 00000000..7647b23a --- /dev/null +++ b/src/Framework/Actions/Internal/FileFinder.php @@ -0,0 +1,66 @@ +|string|false $matchExtensions + * @return \Illuminate\Support\Collection + */ + public static function handle(string $directory, array|string|false $matchExtensions = false, bool $recursive = false): Collection + { + if (! Filesystem::isDirectory($directory)) { + return collect(); + } + + $finder = Finder::create()->files()->in(Hyde::path($directory)); + + if ($recursive === false) { + $finder->depth('== 0'); + } + + if ($matchExtensions !== false) { + $finder->name(static::buildFileExtensionPattern((array) $matchExtensions)); + } + + return collect($finder)->map(function (SplFileInfo $file): string { + return Hyde::pathToRelative($file->getPathname()); + })->sort()->values(); + } + + /** @param array $extensions */ + protected static function buildFileExtensionPattern(array $extensions): string + { + $extensions = self::expandCommaSeparatedValues($extensions); + + return '/\.('.self::normalizeExtensionForRegexPattern($extensions).')$/i'; + } + + /** @param array $extensions */ + private static function expandCommaSeparatedValues(array $extensions): array + { + return array_merge(...array_map(function (string $item): array { + return array_map(fn (string $item): string => trim($item), explode(',', $item)); + }, $extensions)); + } + + /** @param array $extensions */ + private static function normalizeExtensionForRegexPattern(array $extensions): string + { + return implode('|', array_map(function (string $extension): string { + return preg_quote(ltrim($extension, '.'), '/'); + }, $extensions)); + } +} diff --git a/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php b/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php index 849f077b..a0edd4d8 100644 --- a/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php +++ b/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php @@ -10,7 +10,6 @@ use Hyde\Framework\Features\BuildTasks\PreBuildTask; use function basename; -use function glob; use function in_array; use function sprintf; @@ -21,7 +20,7 @@ class CleanSiteDirectory extends PreBuildTask public function handle(): void { if ($this->isItSafeToCleanOutputDirectory()) { - Filesystem::unlink(glob(Hyde::sitePath('*.{html,json}'), GLOB_BRACE)); + Filesystem::unlink(Filesystem::findFiles(Hyde::sitePath(), ['html', 'json'])->all()); Filesystem::cleanDirectory(Hyde::siteMediaPath()); } } diff --git a/src/Support/DataCollections.php b/src/Support/DataCollections.php index e05596c6..91a22906 100644 --- a/src/Support/DataCollections.php +++ b/src/Support/DataCollections.php @@ -12,9 +12,8 @@ use Illuminate\Support\Collection; use Illuminate\Support\Str; -use function implode; +use function Hyde\path_join; use function json_decode; -use function sprintf; use function Hyde\unslash; use function str_starts_with; @@ -102,9 +101,7 @@ public static function json(string $name, bool $asArray = false): static protected static function findFiles(string $name, array|string $extensions): Collection { - return Filesystem::smartGlob(sprintf('%s/%s/*.{%s}', - static::$sourceDirectory, $name, implode(',', (array) $extensions) - ), GLOB_BRACE); + return Filesystem::findFiles(path_join(static::$sourceDirectory, $name), $extensions); } protected static function makeIdentifier(string $path): string diff --git a/src/Support/Filesystem/MediaFile.php b/src/Support/Filesystem/MediaFile.php index 45298a50..09ab5247 100644 --- a/src/Support/Filesystem/MediaFile.php +++ b/src/Support/Filesystem/MediaFile.php @@ -4,6 +4,7 @@ namespace Hyde\Support\Filesystem; +use Hyde\Facades\Filesystem; use Hyde\Hyde; use Hyde\Facades\Config; use Hyde\Framework\Exceptions\FileNotFoundException; @@ -14,12 +15,9 @@ use function array_merge; use function array_keys; use function filesize; -use function implode; use function pathinfo; use function collect; use function is_file; -use function sprintf; -use function glob; /** * File abstraction for a project media file. @@ -104,15 +102,18 @@ protected static function discoverMediaAssetFiles(): array })->all(); } + /** @return array */ protected static function getMediaAssetFiles(): array { - return glob(Hyde::path(static::getMediaGlobPattern()), GLOB_BRACE) ?: []; + return Filesystem::findFiles(Hyde::getMediaDirectory(), static::getMediaFileExtensions(), true)->all(); } - protected static function getMediaGlobPattern(): string + /** @return array|string */ + protected static function getMediaFileExtensions(): array|string { - return sprintf(Hyde::getMediaDirectory().'/{*,**/*,**/*/*}.{%s}', implode(',', - Config::getArray('hyde.media_extensions', self::EXTENSIONS) - )); + /** @var array|string $config */ + $config = Config::get('hyde.media_extensions', self::EXTENSIONS); + + return $config; } } diff --git a/tests/Feature/DiscoveryServiceTest.php b/tests/Feature/DiscoveryServiceTest.php index e545cd64..abafa46c 100644 --- a/tests/Feature/DiscoveryServiceTest.php +++ b/tests/Feature/DiscoveryServiceTest.php @@ -130,7 +130,7 @@ public function testMediaAssetExtensionsCanBeAddedByCommaSeparatedValues() $this->assertSame([], MediaFile::files()); - self::mockConfig(['hyde.media_extensions' => ['1,2,3']]); + self::mockConfig(['hyde.media_extensions' => '1,2,3']); $this->assertSame(['test.1', 'test.2', 'test.3'], MediaFile::files()); } diff --git a/tests/Feature/FileCollectionTest.php b/tests/Feature/FileCollectionTest.php index 2bc4f1d6..cb0d5ba8 100644 --- a/tests/Feature/FileCollectionTest.php +++ b/tests/Feature/FileCollectionTest.php @@ -106,4 +106,20 @@ public function testDocumentationPagesAreDiscovered() $this->assertArrayHasKey('_docs/foo.md', $collection->toArray()); $this->assertEquals(new SourceFile('_docs/foo.md', DocumentationPage::class), $collection->get('_docs/foo.md')); } + + public function testDiscoverFilesForRecursivelyDiscoversFilesInSubdirectories() + { + $this->file('_pages/foo.md'); + $this->file('_pages/foo/bar.md'); + $this->file('_pages/foo/bar/baz.md'); + $collection = FileCollection::init(Hyde::getInstance())->boot(); + + $this->assertArrayHasKey('_pages/foo.md', $collection->toArray()); + $this->assertArrayHasKey('_pages/foo/bar.md', $collection->toArray()); + $this->assertArrayHasKey('_pages/foo/bar/baz.md', $collection->toArray()); + + $this->assertEquals(new SourceFile('_pages/foo.md', MarkdownPage::class), $collection->get('_pages/foo.md')); + $this->assertEquals(new SourceFile('_pages/foo/bar.md', MarkdownPage::class), $collection->get('_pages/foo/bar.md')); + $this->assertEquals(new SourceFile('_pages/foo/bar/baz.md', MarkdownPage::class), $collection->get('_pages/foo/bar/baz.md')); + } } diff --git a/tests/Feature/Foundation/FilesystemTest.php b/tests/Feature/Foundation/FilesystemTest.php index 5527f9ce..25bf1082 100644 --- a/tests/Feature/Foundation/FilesystemTest.php +++ b/tests/Feature/Foundation/FilesystemTest.php @@ -7,22 +7,28 @@ use Hyde\Foundation\HydeKernel; use Hyde\Foundation\Kernel\Filesystem; use Hyde\Foundation\PharSupport; +use Hyde\Framework\Actions\Internal\FileFinder; use Hyde\Hyde; use Hyde\Pages\BladePage; use Hyde\Pages\DocumentationPage; use Hyde\Pages\HtmlPage; use Hyde\Pages\MarkdownPage; use Hyde\Pages\MarkdownPost; +use Hyde\Testing\CreatesTemporaryFiles; use Hyde\Testing\UnitTestCase; +use Illuminate\Support\Collection; use function Hyde\normalize_slashes; /** * @covers \Hyde\Foundation\HydeKernel * @covers \Hyde\Foundation\Kernel\Filesystem + * @covers \Hyde\Facades\Filesystem */ class FilesystemTest extends UnitTestCase { + use CreatesTemporaryFiles; + protected string $originalBasePath; protected Filesystem $filesystem; @@ -361,4 +367,80 @@ public function testPathToRelativeHelperDoesNotModifyNonProjectPaths() $this->assertSame(normalize_slashes($testString), Hyde::pathToRelative($testString)); } } + + public function testFindFileMethodFindsFilesInDirectory() + { + $this->files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md']); + $files = $this->filesystem->findFiles('directory'); + + $this->assertCount(3, $files); + $this->assertContains('directory/apple.md', $files); + $this->assertContains('directory/banana.md', $files); + $this->assertContains('directory/cherry.md', $files); + + $this->cleanUpFilesystem(); + } + + public function testFindFileMethodTypes() + { + $this->file('directory/apple.md'); + $files = $this->filesystem->findFiles('directory'); + + $this->assertInstanceOf(Collection::class, $files); + $this->assertContainsOnly('int', $files->keys()); + $this->assertContainsOnly('string', $files->all()); + $this->assertSame('directory/apple.md', $files->first()); + + $this->cleanUpFilesystem(); + } + + public function testFindFileMethodTypesWithArguments() + { + $this->file('directory/apple.md'); + + $this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', false, false)); + $this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', 'md', false)); + $this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', false, true)); + $this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', 'md', true)); + + $this->cleanUpFilesystem(); + } + + public function testFindFilesFromFilesystemFacade() + { + $this->files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md']); + $files = \Hyde\Facades\Filesystem::findFiles('directory'); + + $this->assertSame(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md'], $files->sort()->values()->all()); + + $this->cleanUpFilesystem(); + } + + public function testFindFilesFromFilesystemFacadeWithArguments() + { + $this->files(['directory/apple.md', 'directory/banana.txt', 'directory/cherry.blade.php', 'directory/nested/dates.md']); + + $files = \Hyde\Facades\Filesystem::findFiles('directory', 'md'); + $this->assertSame(['directory/apple.md'], $files->all()); + + $files = \Hyde\Facades\Filesystem::findFiles('directory', false, true); + $this->assertSame(['directory/apple.md', 'directory/banana.txt', 'directory/cherry.blade.php', 'directory/nested/dates.md'], $files->sort()->values()->all()); + + $this->cleanUpFilesystem(); + } + + public function testCanSwapOutFileFinder() + { + app()->bind(FileFinder::class, function () { + return new class + { + public static function handle(): Collection + { + return collect(['mocked']); + } + }; + }); + + $this->assertSame(['mocked'], \Hyde\Facades\Filesystem::findFiles('directory')->toArray()); + } } diff --git a/tests/Unit/DataCollectionUnitTest.php b/tests/Unit/DataCollectionUnitTest.php index 26791d39..bf15e4ff 100644 --- a/tests/Unit/DataCollectionUnitTest.php +++ b/tests/Unit/DataCollectionUnitTest.php @@ -4,7 +4,7 @@ namespace Hyde\Framework\Testing\Unit; -use Hyde\Hyde; +use Hyde\Framework\Actions\Internal\FileFinder; use Hyde\Support\DataCollections; use Hyde\Testing\UnitTestCase; use Illuminate\Filesystem\Filesystem; @@ -45,18 +45,9 @@ public function testCanConvertCollectionToJson() $this->assertSame('[]', (new DataCollections())->toJson()); } - public function testFindMarkdownFilesCallsProperGlobPattern() - { - $this->mockFilesystemFacade(['shouldReceiveGlob' => true]); - - DataCollections::markdown('foo')->keys()->toArray(); - - $this->verifyMockeryExpectations(); - } - public function testFindMarkdownFilesWithNoFiles() { - $this->mockFilesystemFacade(); + $this->mockFileFinder([]); $this->assertSame([], DataCollections::markdown('foo')->keys()->toArray()); @@ -65,7 +56,7 @@ public function testFindMarkdownFilesWithNoFiles() public function testFindMarkdownFilesWithFiles() { - $this->mockFilesystemFacade(['glob' => ['bar.md']]); + $this->mockFileFinder(['bar.md']); $this->assertSame(['bar.md'], DataCollections::markdown('foo')->keys()->toArray()); @@ -77,25 +68,25 @@ public function testStaticMarkdownHelperReturnsNewDataCollectionInstance() $this->assertInstanceOf(DataCollections::class, DataCollections::markdown('foo')); } - protected function mockFilesystemFacade(array $config = []): void + protected function mockFileFinder(array $files): void { - $defaults = [ - 'exists' => true, - 'glob' => [], - 'get' => 'foo', - ]; + $filesystem = Mockery::mock(Filesystem::class); + $filesystem->shouldReceive('exists')->andReturn(true); + $filesystem->shouldReceive('get')->andReturn('foo'); - $config = array_merge($defaults, $config); + app()->instance(Filesystem::class, $filesystem); - $filesystem = Mockery::mock(Filesystem::class, $config); + $finder = Mockery::mock(FileFinder::class); + $finder->shouldReceive('handle')->andReturn(collect($files)); - if (isset($config['shouldReceiveGlob'])) { - $filesystem->shouldReceive('glob') - ->with(Hyde::path('resources/collections/foo/*.{md}'), GLOB_BRACE) - ->once() - ->andReturn($config['glob']); - } + app()->instance(FileFinder::class, $finder); + } - app()->instance(Filesystem::class, $filesystem); + protected function verifyMockeryExpectations(): void + { + parent::verifyMockeryExpectations(); + + app()->forgetInstance(Filesystem::class); + app()->forgetInstance(FileFinder::class); } } diff --git a/tests/Unit/FileFinderTest.php b/tests/Unit/FileFinderTest.php new file mode 100644 index 00000000..fadf155d --- /dev/null +++ b/tests/Unit/FileFinderTest.php @@ -0,0 +1,276 @@ +files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md']); + $this->assertSameArray(['apple.md', 'banana.md', 'cherry.md'], 'directory'); + } + + public function testFindFilesWithMixedExtensions() + { + $this->files(['directory/apple.md', 'directory/banana.txt', 'directory/cherry.blade.php']); + $this->assertSameArray(['apple.md', 'banana.txt', 'cherry.blade.php'], 'directory'); + } + + public function testFindFilesWithExtension() + { + $this->files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md']); + $this->assertSameArray(['apple.md', 'banana.md', 'cherry.md'], 'directory', 'md'); + } + + public function testFindFilesWithMixedExtensionsReturnsOnlySpecifiedExtension() + { + $this->files(['directory/apple.md', 'directory/banana.txt', 'directory/cherry.blade.php']); + $this->assertSameArray(['apple.md'], 'directory', 'md'); + } + + public function testFindFilesWithRecursive() + { + $this->files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md', 'directory/nested/dates.md']); + $this->assertSameArray(['apple.md', 'banana.md', 'cherry.md', 'nested/dates.md'], 'directory', false, true); + } + + public function testFindFilesWithDeeplyRecursiveFiles() + { + $this->files(['directory/apple.md', 'directory/nested/banana.md', 'directory/nested/deeply/cherry.md']); + $this->assertSameArray(['apple.md', 'nested/banana.md', 'nested/deeply/cherry.md'], 'directory', false, true); + } + + public function testFindFilesWithVeryDeeplyRecursiveFiles() + { + $this->files(['directory/apple.md', 'directory/nested/banana.md', 'directory/nested/deeply/cherry.md', 'directory/nested/very/very/deeply/dates.md', 'directory/nested/very/very/excessively/deeply/elderberries.md']); + $this->assertSameArray(['apple.md', 'nested/banana.md', 'nested/deeply/cherry.md', 'nested/very/very/deeply/dates.md', 'nested/very/very/excessively/deeply/elderberries.md'], 'directory', false, true); + } + + public function testFindFilesIgnoresNestedFilesIfNotRecursive() + { + $this->files(['directory/apple.md', 'directory/nested/banana.md', 'directory/nested/deeply/cherry.md']); + $this->assertSameArray(['apple.md'], 'directory'); + } + + public function testFindFilesReturnsCorrectFilesWhenUsingNestedSubdirectoriesOfDifferentExtensions() + { + $this->files(['directory/apple.md', 'directory/nested/banana.md', 'directory/nested/deeply/cherry.blade.php']); + $this->assertSameArray(['apple.md', 'nested/banana.md'], 'directory', 'md', true); + } + + public function testFindFilesWithFilesHavingNoExtensions() + { + $this->files(['directory/file', 'directory/another_file']); + $this->assertSameArray(['file', 'another_file'], 'directory'); + } + + public function testFindFilesWithSpecialCharactersInNames() + { + $this->files(['directory/file-with-dash.md', 'directory/another_file.txt', 'directory/special@char!.blade.php']); + $this->assertSameArray(['file-with-dash.md', 'another_file.txt', 'special@char!.blade.php'], 'directory'); + } + + public function testFindFilesWithSpecialPrefixes() + { + $this->files(['directory/_file.md', 'directory/-another_file.txt', 'directory/~special_file.blade.php']); + $this->assertSameArray(['_file.md', '-another_file.txt', '~special_file.blade.php'], 'directory'); + } + + public function testFindFilesWithHiddenFiles() + { + $this->files(['directory/.hidden_file', 'directory/.another_hidden.md', 'directory/visible_file.md']); + $this->assertSameArray(['visible_file.md'], 'directory'); + } + + public function testFindFilesWithRecursiveAndHiddenFiles() + { + $this->files(['directory/.hidden_file', 'directory/nested/.another_hidden.md', 'directory/nested/visible_file.md']); + $this->assertSameArray(['nested/visible_file.md'], 'directory', false, true); + } + + public function testFindFilesWithEmptyExtensionFilter() + { + $this->files(['directory/file.md', 'directory/another_file.txt']); + $this->assertSameArray([], 'directory', ''); + } + + public function testFindFilesWithCaseInsensitiveExtensions() + { + $this->files(['directory/file.MD', 'directory/another_file.md', 'directory/ignored.TXT']); + $this->assertSameArray(['file.MD', 'another_file.md'], 'directory', 'md'); + } + + public function testFindFilesWithCaseInsensitiveFilenames() + { + $this->files(['directory/file.md', 'directory/anotherFile.md', 'directory/ANOTHER_FILE.md']); + $this->assertSameArray(['file.md', 'anotherFile.md', 'ANOTHER_FILE.md'], 'directory'); + } + + public function testFindFilesWithCaseInsensitiveExtensionFilter() + { + $this->files(['directory/file.MD', 'directory/another_file.md', 'directory/ignored.TXT']); + $this->assertSameArray(['file.MD', 'another_file.md'], 'directory', 'MD'); + } + + public function testFindFilesWithLeadingDotInFileExtension() + { + $this->files(['directory/file.md', 'directory/another_file.md', 'directory/ignored.txt']); + $this->assertSameArray(['file.md', 'another_file.md'], 'directory', 'md'); + $this->assertSameArray(['file.md', 'another_file.md'], 'directory', '.md'); + } + + public function testFindFilesHandlesLargeNumberOfFiles() + { + $this->files(array_map(fn ($i) => "directory/file$i.md", range(1, 100))); + $this->assertSameArray(array_map(fn ($i) => "file$i.md", range(1, 100)), 'directory'); + } + + public function testFindFilesWithEmptyDirectory() + { + $this->directory('directory'); + $this->assertSameArray([], 'directory'); + } + + public function testFindFilesWithNonExistentDirectory() + { + $this->assertSameArray([], 'nonexistent-directory'); + } + + public function testFindFilesWithMultipleExtensions() + { + $this->files(['directory/file1.md', 'directory/file2.txt', 'directory/file3.blade.php']); + $this->assertSameArray(['file1.md', 'file2.txt'], 'directory', ['md', 'txt']); + } + + public function testFindFilesWithMultipleExtensionsButOnlyOneMatches() + { + $this->files(['directory/file1.md', 'directory/file2.blade.php', 'directory/file3.blade.php']); + $this->assertSameArray(['file1.md'], 'directory', ['md', 'txt']); + } + + public function testFindFilesWithMultipleExtensionsCaseInsensitive() + { + $this->files(['directory/file1.MD', 'directory/file2.TXT', 'directory/file3.blade.PHP']); + $this->assertSameArray(['file1.MD', 'file2.TXT'], 'directory', ['md', 'txt']); + } + + public function testFindFilesWithEmptyArrayExtensions() + { + $this->files(['directory/file1.md', 'directory/file2.txt']); + $this->assertSameArray([], 'directory', []); + } + + public function testFindFilesWithMixedExtensionsAndRecursion() + { + $this->files(['directory/file1.md', 'directory/nested/file2.txt', 'directory/nested/deep/file3.blade.php']); + $this->assertSameArray(['file1.md', 'nested/file2.txt'], 'directory', ['md', 'txt'], true); + } + + public function testFindFilesWithMixedExtensionsNoRecursion() + { + $this->files(['directory/file1.md', 'directory/nested/file2.txt']); + $this->assertSameArray(['file1.md'], 'directory', ['md', 'txt'], false); + } + + public function testFindFilesWithNoFilesMatchingAnyExtension() + { + $this->files(['directory/file1.md', 'directory/file2.txt']); + $this->assertSameArray([], 'directory', ['php', 'html']); + } + + public function testFindFilesWithRecursiveAndNoFilesMatchingAnyExtension() + { + $this->files(['directory/file1.md', 'directory/nested/file2.txt']); + $this->assertSameArray([], 'directory', ['php', 'html'], true); + } + + public function testFindFilesWithRecursiveAndSomeMatchingExtensions() + { + $this->files(['directory/file1.md', 'directory/nested/file2.txt', 'directory/nested/deep/file3.html']); + $this->assertSameArray(['file1.md', 'nested/file2.txt'], 'directory', ['md', 'txt'], true); + } + + public function testFindFilesWithOnlyDotInExtensions() + { + $this->files(['directory/file.md', 'directory/file.txt']); + $this->assertSameArray(['file.md'], 'directory', '.md'); + $this->assertSameArray(['file.txt'], 'directory', '.txt'); + } + + public function testFindFilesWithNoFilesWhenDirectoryContainsUnmatchedExtensions() + { + $this->files(['directory/file.md', 'directory/file.txt']); + $this->assertSameArray([], 'directory', 'php'); + $this->assertSameArray([], 'directory', ['php']); + } + + public function testFindFilesWithEmptyDirectoryAndMultipleExtensions() + { + $this->directory('directory'); + $this->assertSameArray([], 'directory', ['md', 'txt']); + } + + public function testFindFilesWithInvalidExtensionsThrowsNoError() + { + $this->files(['directory/file.md', 'directory/file.txt']); + $this->assertSameArray([], 'directory', ''); + $this->assertSameArray([], 'directory', ['']); + } + + public function testFindFilesWithCsvStringExtensions() + { + $this->files(['directory/file1.md', 'directory/file2.txt', 'directory/file3.jpg']); + $this->assertSameArray(['file1.md', 'file2.txt'], 'directory', 'md,txt'); + } + + public function testFindFilesWithCsvStringExtensionsAndSpaces() + { + $this->files(['directory/file1.md', 'directory/file2.txt', 'directory/file3.jpg']); + $this->assertSameArray(['file1.md', 'file2.txt'], 'directory', 'md, txt'); + } + + public function testFindFilesWithCsvStringExtensionsMixedCase() + { + $this->files(['directory/file1.MD', 'directory/file2.TXT', 'directory/file3.jpg']); + $this->assertSameArray(['file1.MD', 'file2.TXT'], 'directory', 'md,TXT'); + } + + public function testFindFilesWithCsvStringExtensionsInArray() + { + $this->files(['directory/file1.md', 'directory/file2.txt', 'directory/file3.jpg']); + $this->assertSameArray(['file1.md', 'file2.txt'], 'directory', ['md,txt']); + } + + public function testFindFilesWithCsvStringExtensionsInMixedArray() + { + $this->files(['directory/file1.md', 'directory/file2.txt', 'directory/file3.jpg']); + $this->assertSameArray(['file1.md', 'file2.txt', 'file3.jpg'], 'directory', ['md,txt', 'jpg']); + } + + protected function assertSameArray(array $expected, string $directory, string|array|false $matchExtensions = false, bool $recursive = false): void + { + $files = (new Filesystem(Hyde::getInstance()))->findFiles($directory, $matchExtensions, $recursive); + + // Compare sorted arrays because some filesystems may return files in a different order. + $this->assertSame(collect($expected)->map(fn (string $file): string => $directory.'/'.$file)->sort()->values()->all(), $files->all()); + } + + protected function tearDown(): void + { + $this->cleanUpFilesystem(); + } +} diff --git a/tests/Unit/Pages/PageModelGetAllFilesHelperTest.php b/tests/Unit/Pages/PageModelGetAllFilesHelperTest.php deleted file mode 100644 index 6ca5e43d..00000000 --- a/tests/Unit/Pages/PageModelGetAllFilesHelperTest.php +++ /dev/null @@ -1,88 +0,0 @@ -filesystem = $this->mockFilesystemStrict() - ->shouldReceive('missing')->withAnyArgs()->andReturn(false)->byDefault() - ->shouldReceive('get')->withAnyArgs()->andReturn('foo')->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_pages/{*,**/*}.html'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_pages/{*,**/*}.blade.php'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_pages/{*,**/*}.md'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_posts/{*,**/*}.md'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_docs/{*,**/*}.md'), GLOB_BRACE)->andReturn([])->byDefault(); - } - - protected function tearDown(): void - { - $this->verifyMockeryExpectations(); - } - - public function testBladePageGetHelperReturnsBladePageArray() - { - $this->shouldReceiveGlob('_pages/{*,**/*}.blade.php')->andReturn(['_pages/test-page.blade.php']); - - $array = BladePage::files(); - $this->assertCount(1, $array); - $this->assertIsArray($array); - $this->assertEquals(['test-page'], $array); - } - - public function testMarkdownPageGetHelperReturnsMarkdownPageArray() - { - $this->shouldReceiveGlob('_pages/{*,**/*}.md')->andReturn(['_pages/test-page.md']); - - $array = MarkdownPage::files(); - $this->assertCount(1, $array); - $this->assertIsArray($array); - $this->assertEquals(['test-page'], $array); - } - - public function testMarkdownPostGetHelperReturnsMarkdownPostArray() - { - $this->shouldReceiveGlob('_posts/{*,**/*}.md')->andReturn(['_posts/test-post.md']); - - $array = MarkdownPost::files(); - $this->assertCount(1, $array); - $this->assertIsArray($array); - $this->assertEquals(['test-post'], $array); - } - - public function testDocumentationPageGetHelperReturnsDocumentationPageArray() - { - $this->shouldReceiveGlob('_docs/{*,**/*}.md')->andReturn(['_docs/test-page.md']); - - $array = DocumentationPage::files(); - $this->assertCount(1, $array); - $this->assertIsArray($array); - $this->assertEquals(['test-page'], $array); - } - - protected function shouldReceiveGlob(string $withPath): ExpectationInterface - { - return $this->filesystem->shouldReceive('glob')->once()->with(Hyde::path($withPath), GLOB_BRACE); - } -} diff --git a/tests/Unit/Pages/PageModelGetFileHelpersTest.php b/tests/Unit/Pages/PageModelGetFileHelpersTest.php new file mode 100644 index 00000000..0586b311 --- /dev/null +++ b/tests/Unit/Pages/PageModelGetFileHelpersTest.php @@ -0,0 +1,115 @@ +withFile('_pages/test-page.blade.php'); + + $array = BladePage::files(); + $this->assertCount(3, $array); + $this->assertIsArray($array); + $this->assertEquals(['404', 'index', 'test-page'], $array); + } + + public function testMarkdownPageFilesHelperReturnsMarkdownPageArray() + { + $this->withFile('_pages/test-page.md'); + + $array = MarkdownPage::files(); + $this->assertCount(1, $array); + $this->assertIsArray($array); + $this->assertEquals(['test-page'], $array); + } + + public function testMarkdownPostFilesHelperReturnsMarkdownPostArray() + { + $this->withFile('_posts/test-post.md'); + + $array = MarkdownPost::files(); + $this->assertCount(1, $array); + $this->assertIsArray($array); + $this->assertEquals(['test-post'], $array); + } + + public function testDocumentationPageFilesHelperReturnsDocumentationPageArray() + { + $this->withFile('_docs/test-page.md'); + + $array = DocumentationPage::files(); + $this->assertCount(1, $array); + $this->assertIsArray($array); + $this->assertEquals(['test-page'], $array); + } + + public function testBladePageAllHelperReturnsBladePageCollection() + { + $this->withFile('_pages/test-page.blade.php'); + + $collection = BladePage::all(); + + $this->assertCount(3, $collection); + $this->assertInstanceOf(Collection::class, $collection); + $this->assertContainsOnlyInstancesOf(BladePage::class, $collection); + } + + public function testMarkdownPageAllHelperReturnsMarkdownPageCollection() + { + $this->withFile('_pages/test-page.md'); + + $collection = MarkdownPage::all(); + $this->assertCount(1, $collection); + $this->assertInstanceOf(Collection::class, $collection); + $this->assertContainsOnlyInstancesOf(MarkdownPage::class, $collection); + } + + public function testMarkdownPostAllHelperReturnsMarkdownPostCollection() + { + $this->withFile('_posts/test-post.md'); + + $collection = MarkdownPost::all(); + $this->assertCount(1, $collection); + $this->assertInstanceOf(Collection::class, $collection); + $this->assertContainsOnlyInstancesOf(MarkdownPost::class, $collection); + } + + public function testDocumentationPageAllHelperReturnsDocumentationPageCollection() + { + $this->withFile('_docs/test-page.md'); + + $collection = DocumentationPage::all(); + $this->assertCount(1, $collection); + $this->assertInstanceOf(Collection::class, $collection); + $this->assertContainsOnlyInstancesOf(DocumentationPage::class, $collection); + } + + protected function withFile(string $path): void + { + $this->file($path); + + HydeKernel::getInstance()->boot(); + } + + protected function tearDown(): void + { + $this->cleanupFilesystem(); + } +} diff --git a/tests/Unit/Pages/PageModelGetHelperTest.php b/tests/Unit/Pages/PageModelGetHelperTest.php deleted file mode 100644 index cf6232c2..00000000 --- a/tests/Unit/Pages/PageModelGetHelperTest.php +++ /dev/null @@ -1,97 +0,0 @@ -filesystem = $this->mockFilesystemStrict() - ->shouldReceive('glob')->once()->with(Hyde::path('_pages/{*,**/*}.html'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_pages/{*,**/*}.blade.php'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_pages/{*,**/*}.md'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_posts/{*,**/*}.md'), GLOB_BRACE)->andReturn([])->byDefault() - ->shouldReceive('glob')->once()->with(Hyde::path('_docs/{*,**/*}.md'), GLOB_BRACE)->andReturn([])->byDefault(); - } - - protected function tearDown(): void - { - $this->verifyMockeryExpectations(); - } - - public function testBladePageGetHelperReturnsBladePageCollection() - { - $this->shouldReceiveGlob('_pages/{*,**/*}.blade.php')->andReturn(['_pages/test-page.blade.php']); - $this->shouldFindFile('_pages/test-page.blade.php'); - - $collection = BladePage::all(); - $this->assertCount(1, $collection); - $this->assertInstanceOf(Collection::class, $collection); - $this->assertContainsOnlyInstancesOf(BladePage::class, $collection); - } - - public function testMarkdownPageGetHelperReturnsMarkdownPageCollection() - { - $this->shouldReceiveGlob('_pages/{*,**/*}.md')->andReturn(['_pages/test-page.md']); - $this->shouldFindFile('_pages/test-page.md'); - - $collection = MarkdownPage::all(); - $this->assertCount(1, $collection); - $this->assertInstanceOf(Collection::class, $collection); - $this->assertContainsOnlyInstancesOf(MarkdownPage::class, $collection); - } - - public function testMarkdownPostGetHelperReturnsMarkdownPostCollection() - { - $this->shouldReceiveGlob('_posts/{*,**/*}.md')->andReturn(['_posts/test-post.md']); - $this->shouldFindFile('_posts/test-post.md'); - - $collection = MarkdownPost::all(); - $this->assertCount(1, $collection); - $this->assertInstanceOf(Collection::class, $collection); - $this->assertContainsOnlyInstancesOf(MarkdownPost::class, $collection); - } - - public function testDocumentationPageGetHelperReturnsDocumentationPageCollection() - { - $this->shouldReceiveGlob('_docs/{*,**/*}.md')->andReturn(['_docs/test-page.md']); - $this->shouldFindFile('_docs/test-page.md'); - - $collection = DocumentationPage::all(); - $this->assertCount(1, $collection); - $this->assertInstanceOf(Collection::class, $collection); - $this->assertContainsOnlyInstancesOf(DocumentationPage::class, $collection); - } - - protected function shouldReceiveGlob(string $withPath): ExpectationInterface - { - return $this->filesystem->shouldReceive('glob')->once()->with(Hyde::path($withPath), GLOB_BRACE); - } - - protected function shouldFindFile(string $file): void - { - $this->filesystem->shouldReceive('missing')->once()->with(Hyde::path($file))->andReturnFalse(); - $this->filesystem->shouldReceive('get')->once()->with(Hyde::path($file))->andReturn('content'); - } -}