diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml new file mode 100644 index 0000000..885b94d --- /dev/null +++ b/.github/workflows/analyse.yml @@ -0,0 +1,37 @@ +name: analyse + +on: ['push', 'pull_request'] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest] + php: [8.3] + laravel: [11.*] + stability: [prefer-stable] + include: + - laravel: 11.* + testbench: 9.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + - name: Analyse + run: composer analyse \ No newline at end of file diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..161eadb --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,29 @@ +name: "Update Changelog" + +on: + release: + types: [ published, edited, deleted ] + +jobs: + generate: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.release.target_commitish }} + + - name: Generate changelog + uses: justbetter/generate-changelogs-action@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + repository: ${{ github.repository }} + + - name: Commit CHANGELOG + uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: ${{ github.event.release.target_commitish }} + commit_message: Update CHANGELOG + file_pattern: CHANGELOG.md \ No newline at end of file diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..18d1fa0 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,38 @@ +name: coverage + +on: ['push', 'pull_request'] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest] + php: [8.3] + laravel: [11.*] + stability: [prefer-stable] + include: + - laravel: 11.* + testbench: 9.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, xdebug + coverage: xdebug + + - name: Install dependencies + run: | + composer config allow-plugins.pestphp/pest-plugin true + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + - name: Execute tests + run: XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=100 \ No newline at end of file diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..a61cfdc --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,32 @@ +name: style + +on: + push: + branches: + - master +jobs: + style: + name: Style + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install dependencies + run: composer install + + - name: Style + run: composer fix-style + + - name: Commit Changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Fix styling changes \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..bac831d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +name: tests + +on: [ 'push', 'pull_request' ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ ubuntu-latest ] + php: [ 8.2, 8.3 ] + laravel: [ 11.* ] + stability: [ prefer-lowest, prefer-stable ] + include: + - laravel: 11.* + testbench: 9.* + exclude: + - laravel: 11.* + php: 8.1 + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + - name: Execute tests + run: composer test \ No newline at end of file diff --git a/composer.json b/composer.json index bfa5c76..e1c0175 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,19 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1|^8.2|^8.3", "ext-fileinfo": "*", - "statamic/cms": "^4.0 || ^5.0", - "laravel/framework": "^9.50.0 || ^10.0 || ^11.0", + "statamic/cms": "^5.0", + "laravel/framework": "^10.0 || ^11.0", "league/glide": "^2.2" }, "require-dev": { - "laravel/pint": "^1.1", - "orchestra/testbench": "^7.0 || ^8.0", - "phpunit/phpunit": "^9.0 || ^10.0" + "laravel/pint": "^1.7", + "larastan/larastan": "^2.5", + "phpstan/phpstan-mockery": "^1.1", + "phpunit/phpunit": "^10.1", + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^2.0" }, "autoload": { "psr-4": { @@ -38,12 +41,20 @@ }, "scripts": { "test": "phpunit", - "style": "pint --test" + "analyse": "phpstan", + "style": "pint --test", + "quality": [ + "@test", + "@analyse", + "@style" + ], + "fix-style": "pint" }, "config": { "sort-packages": true, "allow-plugins": { - "pixelfear/composer-dist-plugin": true + "pixelfear/composer-dist-plugin": true, + "pestphp/pest-plugin": true } }, "extra": { diff --git a/config/image-optimize.php b/config/image-optimize.php index 079767d..b274569 100644 --- a/config/image-optimize.php +++ b/config/image-optimize.php @@ -18,4 +18,4 @@ // You can exclude containers from optimization entirely here 'excluded_containers' => [], -]; \ No newline at end of file +]; diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..ffdbf4b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +includes: + - ./vendor/larastan/larastan/extension.neon + - ./vendor/phpstan/phpstan-mockery/extension.neon + +parameters: + paths: + - src + - tests + level: 8 + ignoreErrors: + - identifier: missingType.iterableValue diff --git a/phpunit.xml b/phpunit.xml index 6dcf683..91b8cae 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,14 @@ - + ./tests/* - + + ./src - - + + \ No newline at end of file diff --git a/routes/cp.php b/routes/cp.php index 81fc3f4..a96dee0 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -6,7 +6,7 @@ Route::prefix('statamic-image-optimize') ->name('statamic-image-optimize.') ->controller(ImageResizeController::class) - ->group(function() { + ->group(function () { Route::get('/', 'index')->name('index'); Route::get('/resize-images/{forceAll?}', 'resizeImages')->name('resize-images'); Route::get('/resize-images-count/{batchId?}', 'resizeImagesJobCount')->name('resize-images-count'); diff --git a/src/Actions/OptimizeAssets.php b/src/Actions/OptimizeAssets.php index c82d97c..a96eca9 100644 --- a/src/Actions/OptimizeAssets.php +++ b/src/Actions/OptimizeAssets.php @@ -4,32 +4,35 @@ use JustBetter\ImageOptimize\Jobs\ResizeImageJob; use Statamic\Actions\Action; -use Statamic\Contracts\Assets\Asset; +use Statamic\Assets\Asset; class OptimizeAssets extends Action { - public static function title() + public static function title(): string { return __('image-optimize::messages.optimize'); } - public function visibleTo($item) + // @phpstan-ignore-next-line + public function visibleTo($item): bool { return $item instanceof Asset; } - public function visibleToBulk($items) + // @phpstan-ignore-next-line + public function visibleToBulk($items): bool { return $this->visibleTo($items->first()); } - public function run($assets, $values) + // @phpstan-ignore-next-line + public function run($assets, $values): void { - collect($assets) - ->each(function ($asset) { - if ($asset instanceof Asset && $asset->isImage()) { - ResizeImageJob::dispatch($asset); - } + // @phpstan-ignore-next-line + collect($assets ?? []) + ->filter(fn (mixed $asset): bool => $asset instanceof Asset) + ->each(function (Asset $asset): void { + ResizeImageJob::dispatch($asset); }); } } diff --git a/src/Actions/ResizeImage.php b/src/Actions/ResizeImage.php index e8e4cc8..d9ff519 100644 --- a/src/Actions/ResizeImage.php +++ b/src/Actions/ResizeImage.php @@ -13,6 +13,13 @@ class ResizeImage implements ResizesImage { public function resize(Asset $asset, ?int $width = null, ?int $height = null): void { + if (! $asset->exists() || + ! $asset->isImage() || + in_array($asset->containerHandle(), config('image-optimize.excluded_containers')) + ) { + return; + } + $width ??= (int) config('image-optimize.default_resize_width'); $height ??= (int) config('image-optimize.default_resize_height'); @@ -20,7 +27,7 @@ public function resize(Asset $asset, ?int $width = null, ?int $height = null): v try { $orientedImage = Image::make($asset->resolvedPath())->orientate(); - $image = (new Size())->runMaxResize($orientedImage, $width, $height); + $image = (new Size)->runMaxResize($orientedImage, $width, $height); $asset->disk()->filesystem()->put($asset->path(), $image->encode()); @@ -37,6 +44,6 @@ public function resize(Asset $asset, ?int $width = null, ?int $height = null): v public static function bind(): void { - app()->singleton(ResizesImage::class,static::class); + app()->singleton(ResizesImage::class, static::class); } } diff --git a/src/Actions/ResizeImages.php b/src/Actions/ResizeImages.php index 28391e0..d689655 100644 --- a/src/Actions/ResizeImages.php +++ b/src/Actions/ResizeImages.php @@ -2,34 +2,34 @@ namespace JustBetter\ImageOptimize\Actions; +use Illuminate\Bus\Batch; +use Illuminate\Support\Facades\Bus; use JustBetter\ImageOptimize\Contracts\ResizesImages; use JustBetter\ImageOptimize\Events\ImagesResizedEvent; use JustBetter\ImageOptimize\Jobs\ResizeImageJob; use Statamic\Assets\Asset; -use Illuminate\Bus\Batch; -use Illuminate\Support\Facades\Bus; use Statamic\Assets\AssetCollection; +use Statamic\Facades\Asset as AssetFacade; class ResizeImages implements ResizesImages { public function resize(bool $forceAll = false): Batch { /** @var AssetCollection $assets */ - $assets = Asset::all(); + $assets = AssetFacade::all(); - $assets - ->getOptimizableAssets() - ->when(!$forceAll, fn() => $assets->whereNull('image-optimized')); + $assets->getOptimizableAssets() // @phpstan-ignore-line + ->when(! $forceAll, fn () => $assets->whereNull('image-optimized')); $jobs = $assets - ->filter(fn(Asset $asset): bool => $asset->isImage()) + ->filter(fn (Asset $asset): bool => $asset->isImage()) ->map(fn (Asset $asset) => new ResizeImageJob($asset->hydrate())); return Bus::batch($jobs) ->name('image-optimize') ->onConnection(config('image-optimize.default_queue_connection')) ->onQueue(config('image-optimize.default_queue_name')) - ->then(function(): void { + ->then(function (): void { ImagesResizedEvent::dispatch(); }) ->dispatch(); @@ -37,6 +37,6 @@ public function resize(bool $forceAll = false): Batch public static function bind(): void { - app()->singleton(ResizesImages::class,static::class); + app()->singleton(ResizesImages::class, static::class); } } diff --git a/src/Commands/ResizeImagesCommand.php b/src/Commands/ResizeImagesCommand.php index 53705b2..2145564 100644 --- a/src/Commands/ResizeImagesCommand.php +++ b/src/Commands/ResizeImagesCommand.php @@ -3,10 +3,11 @@ namespace JustBetter\ImageOptimize\Commands; use Illuminate\Console\Command; +use Illuminate\Support\Facades\DB; use JustBetter\ImageOptimize\Contracts\ResizesImages; use JustBetter\ImageOptimize\Jobs\ResizeImagesJob; -use Illuminate\Support\Facades\DB; +/** @codeCoverageIgnore */ class ResizeImagesCommand extends Command { protected $signature = 'justbetter:optimize:images {--forceAll}'; @@ -22,14 +23,15 @@ public function handle(ResizesImages $resizesImages): int DB::connection()->getPdo(); } catch (\Exception $e) { $this->error('You need an active database connection in order to use the optimize addon.'); - return false; + + return static::FAILURE; } if ($this->getOutput()->isVerbose()) { - $this->line("Starting the resize images job"); + $this->line('Starting the resize images job'); if ($forceAll) { - $this->comment("Forcing to optimize all images"); + $this->comment('Forcing to optimize all images'); } $batch = $resizesImages->resize($forceAll); @@ -37,7 +39,7 @@ public function handle(ResizesImages $resizesImages): int $progress = $this->output->createProgressBar($batch->totalJobs); $progress->start(); - while($batch->pendingJobs && !$batch->finished() && !$batch->cancelled()) { + while ($batch->pendingJobs && ! $batch->finished() && ! $batch->cancelled()) { $batch = $batch->fresh(); $progress->setProgress($batch->processedJobs()); } @@ -45,10 +47,10 @@ public function handle(ResizesImages $resizesImages): int $progress->finish(); $this->output->newLine(2); - $this->info("All images have been resized"); + $this->info('All images have been resized'); } else { ResizeImagesJob::dispatch($forceAll); - $this->info("Jobs dispatched"); + $this->info('Jobs dispatched'); } return static::SUCCESS; diff --git a/src/Contracts/ResizesImage.php b/src/Contracts/ResizesImage.php index 345f903..0aabc2d 100644 --- a/src/Contracts/ResizesImage.php +++ b/src/Contracts/ResizesImage.php @@ -7,4 +7,4 @@ interface ResizesImage { public function resize(Asset $asset, ?int $width = null, ?int $height = null): void; -} \ No newline at end of file +} diff --git a/src/Http/Controllers/CP/ImageResizeController.php b/src/Http/Controllers/CP/ImageResizeController.php index 9dd34a3..2dd9914 100644 --- a/src/Http/Controllers/CP/ImageResizeController.php +++ b/src/Http/Controllers/CP/ImageResizeController.php @@ -2,6 +2,8 @@ namespace JustBetter\ImageOptimize\Http\Controllers\CP; +use Illuminate\Contracts\View\Factory; +use Illuminate\Contracts\View\View; use Illuminate\Http\JsonResponse; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Bus; @@ -11,9 +13,12 @@ class ImageResizeController extends Controller { - public function index() : string + /** + * @codeCoverageIgnore + */ + public function index(): Factory|View|string { - $assets = Asset::all()->getOptimizableAssets(); + $assets = Asset::all()->getOptimizableAssets(); // @phpstan-ignore-line $unoptimizedAssets = $assets->whereNull('image-optimized'); $databaseConnected = true; @@ -31,30 +36,29 @@ public function index() : string ]); } - public function resizeImages(ResizesImages $resizesImages, string $forceAll = null): JsonResponse + public function resizeImages(ResizesImages $resizesImages, ?string $forceAll = null): JsonResponse { $batch = $resizesImages->resize($forceAll !== null); return response()->json([ 'imagesOptimized' => true, - 'batchId' => $batch->id + 'batchId' => $batch->id, ]); } - public function resizeImagesJobCount(string $batchId = null): JsonResponse + public function resizeImagesJobCount(?string $batchId = null): JsonResponse { $batch = $batchId ? Bus::findBatch($batchId) : null; if ($batch) { return response()->json([ - 'assetsToOptimize' => $batch->pendingJobs ?? 0, - 'assetTotal' => $batch->totalJobs ?? 0 + 'assetsToOptimize' => $batch->pendingJobs, + 'assetTotal' => $batch->totalJobs, ]); } $allAssets = Asset::all(); - $assets = $allAssets - ->getOptimizableAssets() + $assets = $allAssets->getOptimizableAssets() // @phpstan-ignore-line ->whereNull('image-optimized'); return response()->json([ diff --git a/src/Jobs/ResizeImageJob.php b/src/Jobs/ResizeImageJob.php index 3d82917..3fedfe8 100644 --- a/src/Jobs/ResizeImageJob.php +++ b/src/Jobs/ResizeImageJob.php @@ -2,6 +2,7 @@ namespace JustBetter\ImageOptimize\Jobs; +use Illuminate\Bus\Batchable; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; @@ -9,14 +10,13 @@ use Illuminate\Queue\InteractsWithQueue; use JustBetter\ImageOptimize\Contracts\ResizesImage; use Statamic\Assets\Asset; -use Illuminate\Bus\Batchable; -class ResizeImageJob implements ShouldQueue, ShouldBeUnique +class ResizeImageJob implements ShouldBeUnique, ShouldQueue { + use Batchable; use Dispatchable; use InteractsWithQueue; use Queueable; - use Batchable; public function __construct( public Asset $asset, diff --git a/src/Jobs/ResizeImagesJob.php b/src/Jobs/ResizeImagesJob.php index e9292bb..1d9b2d7 100644 --- a/src/Jobs/ResizeImagesJob.php +++ b/src/Jobs/ResizeImagesJob.php @@ -9,7 +9,7 @@ use Illuminate\Queue\InteractsWithQueue; use JustBetter\ImageOptimize\Contracts\ResizesImages; -class ResizeImagesJob implements ShouldQueue, ShouldBeUnique +class ResizeImagesJob implements ShouldBeUnique, ShouldQueue { use Dispatchable; use InteractsWithQueue; diff --git a/src/Listeners/AssetUploadedListener.php b/src/Listeners/AssetUploadedListener.php index 5b31db1..feec700 100644 --- a/src/Listeners/AssetUploadedListener.php +++ b/src/Listeners/AssetUploadedListener.php @@ -2,10 +2,10 @@ namespace JustBetter\ImageOptimize\Listeners; +use JustBetter\ImageOptimize\Jobs\ResizeImageJob; use Statamic\Assets\Asset; -use Statamic\Events\AssetUploaded; use Statamic\Events\AssetReuploaded; -use JustBetter\ImageOptimize\Jobs\ResizeImageJob; +use Statamic\Events\AssetUploaded; class AssetUploadedListener { @@ -14,12 +14,6 @@ public function handle(AssetUploaded|AssetReuploaded $event): void /** @var Asset $asset */ $asset = $event->asset; - if (!$asset->exists()) { - return; - } - - if ($asset->isImage() && !in_array($asset->containerHandle(), config('image-optimize.excluded_containers'))) { - ResizeImageJob::dispatch($asset); - } + ResizeImageJob::dispatch($asset); } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index bc356eb..26bf7b3 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -6,12 +6,13 @@ use JustBetter\ImageOptimize\Actions\ResizeImage; use JustBetter\ImageOptimize\Actions\ResizeImages; use JustBetter\ImageOptimize\Commands\ResizeImagesCommand; +use JustBetter\ImageOptimize\Listeners\AssetUploadedListener; use Statamic\Assets\AssetCollection; +use Statamic\CP\Navigation\Nav as Navigation; +use Statamic\Events\AssetReuploaded; +use Statamic\Events\AssetUploaded; use Statamic\Facades\CP\Nav; use Statamic\Providers\AddonServiceProvider; -use JustBetter\ImageOptimize\Listeners\AssetUploadedListener; -use Statamic\Events\AssetUploaded; -use Statamic\Events\AssetReuploaded; class ServiceProvider extends AddonServiceProvider { @@ -20,11 +21,11 @@ class ServiceProvider extends AddonServiceProvider ]; protected $routes = [ - 'cp' => __DIR__ . '/../routes/cp.php' + 'cp' => __DIR__.'/../routes/cp.php', ]; protected $scripts = [ - __DIR__ . '/../dist/js/statamic-image-optimize.js' + __DIR__.'/../dist/js/statamic-image-optimize.js', ]; public function register(): void @@ -34,7 +35,6 @@ public function register(): void ->registerMacros(); } - protected function registerConfig(): static { $this->mergeConfigFrom(__DIR__.'/../config/image-optimize.php', 'image-optimize'); @@ -65,7 +65,7 @@ public function boot(): void { parent::boot(); - $this->loadViewsFrom(__DIR__ . '/../resources/views', 'statamic-image-optimize'); + $this->loadViewsFrom(__DIR__.'/../resources/views', 'statamic-image-optimize'); $this->bootPublishables() ->bootEvents() @@ -74,8 +74,6 @@ public function boot(): void ->handleTranslations(); } - - public function bootEvents(): static { Event::listen([AssetUploaded::class, AssetReuploaded::class], AssetUploadedListener::class); @@ -86,7 +84,7 @@ public function bootEvents(): static protected function bootCommands(): static { $this->commands([ - ResizeImagesCommand::class + ResizeImagesCommand::class, ]); return $this; @@ -101,9 +99,12 @@ protected function bootPublishables(): static return $this; } + /** + * @codeCoverageIgnore + */ protected function bootNav(): static { - Nav::extend(function ($nav) { + Nav::extend(function (Navigation $nav): void { $nav->create('Image Optimize') ->section('Tools') ->route('statamic-image-optimize.index') @@ -115,10 +116,10 @@ protected function bootNav(): static protected function handleTranslations(): static { - $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'image-optimize'); + $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'image-optimize'); $this->publishes([ - __DIR__ . '/../resources/lang' => resource_path('lang/vendor/statamic-image-optimize'), + __DIR__.'/../resources/lang' => resource_path('lang/vendor/statamic-image-optimize'), ], 'image-optimize-translations'); return $this; diff --git a/tests/Actions/OptimizeAssetsTest.php b/tests/Actions/OptimizeAssetsTest.php new file mode 100644 index 0000000..067e7b0 --- /dev/null +++ b/tests/Actions/OptimizeAssetsTest.php @@ -0,0 +1,65 @@ +assertEquals('Optimize', OptimizeAssets::title()); + } + + #[Test] + public function it_can_be_visible(): void + { + $asset = $this->createAsset(); + + /** @var OptimizeAssets $action */ + $action = app(OptimizeAssets::class); + + $this->assertTrue( + $action->visibleTo($asset) + ); + } + + #[Test] + public function it_can_be_visible_to_bulk(): void + { + $assets = collect([ + $this->createAsset(), + ]); + + /** @var OptimizeAssets $action */ + $action = app(OptimizeAssets::class); + + $this->assertTrue( + $action->visibleToBulk($assets) + ); + } + + #[Test] + public function it_can_run(): void + { + Bus::fake(); + + $assets = collect([ + $this->createAsset(), + 'asset', + false, + null, + ]); + + /** @var OptimizeAssets $action */ + $action = app(OptimizeAssets::class); + $action->run($assets, null); + + Bus::assertDispatched(ResizeImageJob::class, 1); + } +} diff --git a/tests/Actions/ResizeImageTest.php b/tests/Actions/ResizeImageTest.php new file mode 100644 index 0000000..7fc1270 --- /dev/null +++ b/tests/Actions/ResizeImageTest.php @@ -0,0 +1,68 @@ +createAsset(); + + /** @var ResizeImage $action */ + $action = app(ResizeImage::class); + $action->resize($asset, 100, 100); + + $this->assertEquals(100, $asset->meta('width')); + $this->assertEquals(63, $asset->meta('height')); + $this->assertEquals(1, $asset->meta('data.image-optimized')); + + Event::assertDispatched(ImageResizedEvent::class); + } + + #[Test] + public function it_can_ignore_excluded_containers(): void + { + Event::fake(); + + config()->set('image-optimize.excluded_containers', [ + 'test_container', + ]); + + $asset = $this->createAsset(); + + /** @var ResizeImage $action */ + $action = app(ResizeImage::class); + $action->resize($asset); + + Event::assertNotDispatched(ImageResizedEvent::class); + } + + #[Test] + public function it_can_catch_exceptions(): void + { + Event::fake(); + + Image::spy() + ->shouldReceive('make') + ->andThrow(NotReadableException::class); + + $asset = $this->createAsset(); + + /** @var ResizeImage $action */ + $action = app(ResizeImage::class); + $action->resize($asset); + + Event::assertNotDispatched(ImageResizedEvent::class); + } +} diff --git a/tests/Actions/ResizeImagesTest.php b/tests/Actions/ResizeImagesTest.php new file mode 100644 index 0000000..c1318b8 --- /dev/null +++ b/tests/Actions/ResizeImagesTest.php @@ -0,0 +1,50 @@ +createAsset(); + + /** @var ResizeImages $action */ + $action = app(ResizeImages::class); + $action->resize(); + + Bus::assertBatched(fn (PendingBatchFake $batch): bool => $batch->jobs->count() === 1); + } + + #[Test] + #[WithMigration('queue')] + public function it_can_dispatch_events(): void + { + Bus::fake(); + Event::fake(); + + /** @var ResizeImages $action */ + $action = app(ResizeImages::class); + + $batch = $action->resize(); + + /** @var non-empty-array $thenCallbacks */ + $thenCallbacks = $batch->then; // @phpstan-ignore-line + + call_user_func($thenCallbacks[0], $batch); + + Event::assertDispatched(ImagesResizedEvent::class); + } +} diff --git a/tests/Http/Controllers/ImageResizeControllerTest.php b/tests/Http/Controllers/ImageResizeControllerTest.php new file mode 100644 index 0000000..26bf788 --- /dev/null +++ b/tests/Http/Controllers/ImageResizeControllerTest.php @@ -0,0 +1,73 @@ +toImmutable()); + + $this->mock(ResizesImages::class, function (MockInterface $mock) use ($fakeBatch): void { + $mock + ->shouldReceive('resize') + ->andReturn($fakeBatch); + }); + + $this + ->withoutMiddleware() + ->get(route('statamic.cp.statamic-image-optimize.resize-images')) + ->assertSuccessful() + ->assertJson([ + 'imagesOptimized' => true, + 'batchId' => '::batch-id::', + ]); + } + + #[Test] + public function it_can_get_resize_images_count(): void + { + Bus::fake(); + + $this->createAsset(); + + $this + ->withoutMiddleware() + ->get(route('statamic.cp.statamic-image-optimize.resize-images-count')) + ->assertSuccessful() + ->assertJson([ + 'assetsToOptimize' => 1, + 'assetTotal' => 1, + ]); + } + + #[Test] + public function it_can_get_resize_images_count_with_batch(): void + { + Bus::fake(); + + $this->createAsset(); + $fakeBatch = new BatchFake('::batch-id::', '::name::', 1, 0, 0, [], [], now()->toImmutable()); + + Bus::spy() + ->shouldReceive('findBatch') + ->andReturn($fakeBatch); + + $this + ->withoutMiddleware() + ->get(route('statamic.cp.statamic-image-optimize.resize-images-count', ['batchId' => '::batch-id::'])) + ->assertSuccessful() + ->assertJson([ + 'assetsToOptimize' => 0, + 'assetTotal' => 1, + ]); + } +} diff --git a/tests/Jobs/ResizeImageJobTest.php b/tests/Jobs/ResizeImageJobTest.php new file mode 100644 index 0000000..5cbce9f --- /dev/null +++ b/tests/Jobs/ResizeImageJobTest.php @@ -0,0 +1,26 @@ +createAsset(); + + $this->mock(ResizesImage::class, function (MockInterface $mock): void { + $mock + ->shouldReceive('resize') + ->once(); + }); + + ResizeImageJob::dispatch($asset, 100, 100); + } +} diff --git a/tests/Jobs/ResizeImagesJobTest.php b/tests/Jobs/ResizeImagesJobTest.php new file mode 100644 index 0000000..1e68613 --- /dev/null +++ b/tests/Jobs/ResizeImagesJobTest.php @@ -0,0 +1,39 @@ +mock(ResizesImages::class, function (MockInterface $mock) use ($forceAll): void { + $mock + ->shouldReceive('resize') + ->with($forceAll) + ->once(); + }); + + ResizeImagesJob::dispatch($forceAll); + } + + public static function cases(): array + { + return [ + 'true' => [ + 'forceAll' => true, + ], + 'false' => [ + 'forceAll' => false, + ], + ]; + } +} diff --git a/tests/Listeners/AssetUploadedListenerTest.php b/tests/Listeners/AssetUploadedListenerTest.php new file mode 100644 index 0000000..54c121f --- /dev/null +++ b/tests/Listeners/AssetUploadedListenerTest.php @@ -0,0 +1,29 @@ +createAsset(); + + $event = new AssetUploaded($asset); + + /** @var AssetUploadedListener $listener */ + $listener = app(AssetUploadedListener::class); + $listener->handle($event); + + Bus::assertDispatched(ResizeImageJob::class); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index d424235..9b90ef9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,15 +2,85 @@ namespace JustBetter\ImageOptimize\Tests; +use Illuminate\Foundation\Testing\Concerns\InteractsWithViews; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Support\Facades\Storage; +use Intervention\Image\ImageServiceProvider; use JustBetter\ImageOptimize\ServiceProvider; -use Orchestra\Testbench\TestCase as BaseTestCase; +use Statamic\Assets\Asset; +use Statamic\Assets\AssetContainer; +use Statamic\Testing\AddonTestCase; +use Statamic\Testing\Concerns\PreventsSavingStacheItemsToDisk; -abstract class TestCase extends BaseTestCase +abstract class TestCase extends AddonTestCase { - protected function getPackageProviders($app): array + use InteractsWithViews; + use LazilyRefreshDatabase; + use PreventsSavingStacheItemsToDisk; + + protected string $addonServiceProvider = ServiceProvider::class; + + protected ?AssetContainer $assetContainer = null; + + protected function getPackageProviders($app) + { + return array_merge(parent::getPackageProviders($app), [ + ImageServiceProvider::class, + ]); + } + + protected function defineEnvironment($app): void + { + $app['config']->set('app.key', 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF'); + + $app['config']->set('statamic.assets.image_manipulation.driver', 'gd'); + + $app['config']->set('filesystems.disks.assets', [ + 'driver' => 'local', + 'root' => $this->fixturePath('assets'), + ]); + + $app['config']->set('database.default', 'testbench'); + $app['config']->set('queue.batching.database', 'testbench'); + $app['config']->set('queue.failed.database', 'testbench'); + $app['config']->set('database.connections.testbench', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } + + protected function assetContainer(): AssetContainer { - return [ - ServiceProvider::class, - ]; + if ($this->assetContainer === null) { + $this->assetContainer = (new AssetContainer) // @phpstan-ignore-line + ->handle('test_container') + ->disk('assets') + ->save(); + } + + return $this->assetContainer; + } + + protected function fixturePath(string $file = ''): string + { + $path = __DIR__.'/__fixtures__'; + + if (strlen($file) > 0) { + $path .= '/'.$file; + } + + return $path; + } + + protected function createAsset(string $filename = 'test.png'): Asset + { + Storage::disk('assets')->put($filename, file_get_contents($this->fixturePath('uploads/test.png'))); // @phpstan-ignore-line + + /** @var Asset $asset */ + $asset = (new Asset)->container($this->assetContainer())->path($filename); // @phpstan-ignore-line + $asset->save(); + + return $asset; } } diff --git a/tests/__fixtures__/assets/.gitignore b/tests/__fixtures__/assets/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/__fixtures__/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/__fixtures__/content/assets/test_container.yaml b/tests/__fixtures__/content/assets/test_container.yaml new file mode 100644 index 0000000..1bb97e5 --- /dev/null +++ b/tests/__fixtures__/content/assets/test_container.yaml @@ -0,0 +1 @@ +disk: assets diff --git a/tests/__fixtures__/dev-null/.gitkeep b/tests/__fixtures__/dev-null/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/__fixtures__/uploads/test.png b/tests/__fixtures__/uploads/test.png new file mode 100644 index 0000000..1eceb0c Binary files /dev/null and b/tests/__fixtures__/uploads/test.png differ