diff --git a/app/Console/Commands/GenerateDefaultImagesCommand.php b/app/Console/Commands/GenerateDefaultImagesCommand.php new file mode 100644 index 0000000..c326dd6 --- /dev/null +++ b/app/Console/Commands/GenerateDefaultImagesCommand.php @@ -0,0 +1,197 @@ +info('Starting generation of default images for all device models...'); + + $deviceModels = DeviceModel::all(); + + if ($deviceModels->isEmpty()) { + $this->warn('No device models found in the database.'); + + return self::SUCCESS; + } + + $this->info("Found {$deviceModels->count()} device models to process."); + + // Create the target directory + $targetDir = 'images/default-screens'; + if (! Storage::disk('public')->exists($targetDir)) { + Storage::disk('public')->makeDirectory($targetDir); + $this->info("Created directory: {$targetDir}"); + } + + $successCount = 0; + $skipCount = 0; + $errorCount = 0; + + foreach ($deviceModels as $deviceModel) { + $this->info("Processing device model: {$deviceModel->label} (ID: {$deviceModel->id})"); + + try { + // Process setup-logo + $setupResult = $this->transformImage('setup-logo', $deviceModel, $targetDir); + if ($setupResult) { + ++$successCount; + } else { + ++$skipCount; + } + + // Process sleep + $sleepResult = $this->transformImage('sleep', $deviceModel, $targetDir); + if ($sleepResult) { + ++$successCount; + } else { + ++$skipCount; + } + + } catch (Exception $e) { + $this->error("Error processing device model {$deviceModel->label}: ".$e->getMessage()); + ++$errorCount; + } + } + + $this->info("\nGeneration completed!"); + $this->info("Successfully processed: {$successCount} images"); + $this->info("Skipped (already exist): {$skipCount} images"); + $this->info("Errors: {$errorCount} images"); + + return self::SUCCESS; + } + + /** + * Transform a single image for a device model using Blade templates + */ + private function transformImage(string $imageType, DeviceModel $deviceModel, string $targetDir): bool + { + // Generate filename: {width}_{height}_{bit_depth}_{rotation}.{extension} + $extension = $deviceModel->mime_type === 'image/bmp' ? 'bmp' : 'png'; + $filename = "{$deviceModel->width}_{$deviceModel->height}_{$deviceModel->bit_depth}_{$deviceModel->rotation}.{$extension}"; + $targetPath = "{$targetDir}/{$imageType}_{$filename}"; + + // Check if target already exists and force is not set + if (Storage::disk('public')->exists($targetPath) && ! $this->option('force')) { + $this->line(" Skipping {$imageType} - already exists: {$filename}"); + + return false; + } + + try { + // Create custom Browsershot instance if using AWS Lambda + $browsershotInstance = null; + if (config('app.puppeteer_mode') === 'sidecar-aws') { + $browsershotInstance = new BrowsershotLambda(); + } + + // Generate HTML from Blade template + $html = $this->generateHtmlFromTemplate($imageType, $deviceModel); + // dump($html); + + $browserStage = new BrowserStage($browsershotInstance); + $browserStage->html($html); + $browserStage + ->width($deviceModel->width) + ->height($deviceModel->height); + + $browserStage->setBrowsershotOption('waitUntil', 'networkidle0'); + + if (config('app.puppeteer_docker')) { + $browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']); + } + + $outputPath = Storage::disk('public')->path($targetPath); + + $imageStage = new ImageStage(); + $imageStage->format($extension) + ->width($deviceModel->width) + ->height($deviceModel->height) + ->colors($deviceModel->colors) + ->bitDepth($deviceModel->bit_depth) + ->rotation($deviceModel->rotation) + // ->offsetX($deviceModel->offset_x) + // ->offsetY($deviceModel->offset_y) + ->outputPath($outputPath); + + (new TrmnlPipeline())->pipe($browserStage) + ->pipe($imageStage) + ->process(); + + if (! file_exists($outputPath)) { + throw new RuntimeException('Image file was not created: '.$outputPath); + } + + if (filesize($outputPath) === 0) { + throw new RuntimeException('Image file is empty: '.$outputPath); + } + + $this->line(" ✓ Generated {$imageType}: {$filename}"); + + return true; + + } catch (Exception $e) { + $this->error(" ✗ Failed to generate {$imageType} for {$deviceModel->label}: ".$e->getMessage()); + + return false; + } + } + + /** + * Generate HTML from Blade template for the given image type and device model + */ + private function generateHtmlFromTemplate(string $imageType, DeviceModel $deviceModel): string + { + // Map image type to template name + $templateName = match ($imageType) { + 'setup-logo' => 'default-screens.setup', + 'sleep' => 'default-screens.sleep', + default => throw new InvalidArgumentException("Invalid image type: {$imageType}") + }; + + // Determine device properties from DeviceModel + $deviceVariant = $deviceModel->name ?? 'og'; + $colorDepth = $deviceModel->color_depth ?? '1bit'; // Use the accessor method + $scaleLevel = $deviceModel->scale_level; // Use the accessor method + $darkMode = $imageType === 'sleep'; // Sleep mode uses dark mode, setup uses light mode + + // Render the Blade template + return view($templateName, [ + 'noBleed' => false, + 'darkMode' => $darkMode, + 'deviceVariant' => $deviceVariant, + 'colorDepth' => $colorDepth, + 'scaleLevel' => $scaleLevel, + ])->render(); + } +} diff --git a/app/Models/DeviceModel.php b/app/Models/DeviceModel.php index 0d3757b..4dfaf1e 100644 --- a/app/Models/DeviceModel.php +++ b/app/Models/DeviceModel.php @@ -31,6 +31,10 @@ final class DeviceModel extends Model return null; } + if ($this->bit_depth === 3) { + return '2bit'; + } + // if higher then 4 return 4bit if ($this->bit_depth > 4) { return '4bit'; diff --git a/app/Services/ImageGenerationService.php b/app/Services/ImageGenerationService.php index 36597d7..f513e05 100644 --- a/app/Services/ImageGenerationService.php +++ b/app/Services/ImageGenerationService.php @@ -12,6 +12,7 @@ use Bnussbau\TrmnlPipeline\TrmnlPipeline; use Exception; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; +use InvalidArgumentException; use Ramsey\Uuid\Uuid; use RuntimeException; use Wnx\SidecarBrowsershot\BrowsershotLambda; @@ -255,4 +256,151 @@ class ImageGenerationService } } } + + /** + * Get device-specific default image path for setup or sleep mode + */ + public static function getDeviceSpecificDefaultImage(Device $device, string $imageType): ?string + { + // Validate image type + if (! in_array($imageType, ['setup-logo', 'sleep'])) { + return null; + } + + // If device has a DeviceModel, try to find device-specific image + if ($device->deviceModel) { + $model = $device->deviceModel; + $extension = $model->mime_type === 'image/bmp' ? 'bmp' : 'png'; + $filename = "{$model->width}_{$model->height}_{$model->bit_depth}_{$model->rotation}.{$extension}"; + $deviceSpecificPath = "images/default-screens/{$imageType}_{$filename}"; + + if (Storage::disk('public')->exists($deviceSpecificPath)) { + return $deviceSpecificPath; + } + } + + // Fallback to original hardcoded images + $fallbackPath = "images/{$imageType}.bmp"; + if (Storage::disk('public')->exists($fallbackPath)) { + return $fallbackPath; + } + + // Try PNG fallback + $fallbackPathPng = "images/{$imageType}.png"; + if (Storage::disk('public')->exists($fallbackPathPng)) { + return $fallbackPathPng; + } + + return null; + } + + /** + * Generate a default screen image from Blade template + */ + public static function generateDefaultScreenImage(Device $device, string $imageType): string + { + // Validate image type + if (! in_array($imageType, ['setup-logo', 'sleep'])) { + throw new InvalidArgumentException("Invalid image type: {$imageType}"); + } + + $uuid = Uuid::uuid4()->toString(); + + try { + // Get image generation settings from DeviceModel if available, otherwise use device settings + $imageSettings = self::getImageSettings($device); + + $fileExtension = $imageSettings['mime_type'] === 'image/bmp' ? 'bmp' : 'png'; + $outputPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.'.$fileExtension); + + // Generate HTML from Blade template + $html = self::generateDefaultScreenHtml($device, $imageType); + + // Create custom Browsershot instance if using AWS Lambda + $browsershotInstance = null; + if (config('app.puppeteer_mode') === 'sidecar-aws') { + $browsershotInstance = new BrowsershotLambda(); + } + + $browserStage = new BrowserStage($browsershotInstance); + $browserStage->html($html); + + if (config('app.puppeteer_window_size_strategy') === 'v2') { + $browserStage + ->width($imageSettings['width']) + ->height($imageSettings['height']); + } else { + $browserStage->useDefaultDimensions(); + } + + if (config('app.puppeteer_wait_for_network_idle')) { + $browserStage->setBrowsershotOption('waitUntil', 'networkidle0'); + } + + if (config('app.puppeteer_docker')) { + $browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']); + } + + $imageStage = new ImageStage(); + $imageStage->format($fileExtension) + ->width($imageSettings['width']) + ->height($imageSettings['height']) + ->colors($imageSettings['colors']) + ->bitDepth($imageSettings['bit_depth']) + ->rotation($imageSettings['rotation']) + ->offsetX($imageSettings['offset_x']) + ->offsetY($imageSettings['offset_y']) + ->outputPath($outputPath); + + (new TrmnlPipeline())->pipe($browserStage) + ->pipe($imageStage) + ->process(); + + if (! file_exists($outputPath)) { + throw new RuntimeException('Image file was not created: '.$outputPath); + } + + if (filesize($outputPath) === 0) { + throw new RuntimeException('Image file is empty: '.$outputPath); + } + + Log::info("Device $device->id: generated default screen image: $uuid for type: $imageType"); + + return $uuid; + + } catch (Exception $e) { + Log::error('Failed to generate default screen image: '.$e->getMessage()); + throw new RuntimeException('Failed to generate default screen image: '.$e->getMessage(), 0, $e); + } + } + + /** + * Generate HTML from Blade template for default screens + */ + private static function generateDefaultScreenHtml(Device $device, string $imageType): string + { + // Map image type to template name + $templateName = match ($imageType) { + 'setup-logo' => 'default-screens.setup', + 'sleep' => 'default-screens.sleep', + default => throw new InvalidArgumentException("Invalid image type: {$imageType}") + }; + + // Determine device properties from DeviceModel or device settings + $deviceVariant = $device->deviceVariant(); + $deviceOrientation = $device->rotate > 0 ? 'portrait' : 'landscape'; + $colorDepth = $device->colorDepth() ?? '1bit'; + $scaleLevel = $device->scaleLevel(); + $darkMode = $imageType === 'sleep'; // Sleep mode uses dark mode, setup uses light mode + + // Render the Blade template + return view($templateName, [ + 'noBleed' => false, + 'darkMode' => $darkMode, + 'deviceVariant' => $deviceVariant, + 'deviceOrientation' => $deviceOrientation, + 'colorDepth' => $colorDepth, + 'scaleLevel' => $scaleLevel, + ])->render(); + } } diff --git a/resources/views/default-screens/setup.blade.php b/resources/views/default-screens/setup.blade.php new file mode 100644 index 0000000..3b0ff05 --- /dev/null +++ b/resources/views/default-screens/setup.blade.php @@ -0,0 +1,22 @@ +@props([ + 'noBleed' => false, + 'darkMode' => false, + 'deviceVariant' => 'og', + 'deviceOrientation' => null, + 'colorDepth' => '1bit', + 'scaleLevel' => null, +]) + + + + + + Welcome to BYOS Laravel! + Your device is connected. + + + + + diff --git a/resources/views/default-screens/sleep.blade.php b/resources/views/default-screens/sleep.blade.php new file mode 100644 index 0000000..89d6baa --- /dev/null +++ b/resources/views/default-screens/sleep.blade.php @@ -0,0 +1,28 @@ +@props([ + 'noBleed' => false, + 'darkMode' => true, + 'deviceVariant' => 'og', + 'deviceOrientation' => null, + 'colorDepth' => '1bit', + 'scaleLevel' => null, +]) + + + + + +
+ + + +
+ Sleep Mode +
+
+ +
+
diff --git a/routes/api.php b/routes/api.php index 8adc404..9721a0f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -60,12 +60,22 @@ Route::get('/display', function (Request $request) { } if ($device->isPauseActive()) { - $image_path = 'images/sleep.png'; - $filename = 'sleep.png'; + $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep'); + if (! $image_path) { + // Generate from template if no device-specific image exists + $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep'); + $image_path = 'images/generated/'.$image_uuid.'.png'; + } + $filename = basename($image_path); $refreshTimeOverride = (int) now()->diffInSeconds($device->pause_until); } elseif ($device->isSleepModeActive()) { - $image_path = 'images/sleep.png'; - $filename = 'sleep.png'; + $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep'); + if (! $image_path) { + // Generate from template if no device-specific image exists + $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep'); + $image_path = 'images/generated/'.$image_uuid.'.png'; + } + $filename = basename($image_path); $refreshTimeOverride = $device->getSleepModeEndsInSeconds() ?? $device->default_refresh_interval; } else { // Get current screen image from a mirror device or continue if not available @@ -125,8 +135,13 @@ Route::get('/display', function (Request $request) { $image_uuid = $device->current_screen_image; } if (! $image_uuid) { - $image_path = 'images/setup-logo.bmp'; - $filename = 'setup-logo.bmp'; + $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo'); + if (! $image_path) { + // Generate from template if no device-specific image exists + $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo'); + $image_path = 'images/generated/'.$image_uuid.'.png'; + } + $filename = basename($image_path); } else { // Determine image format based on device settings $preferred_format = 'png'; // Default to PNG for newer firmware @@ -225,7 +240,7 @@ Route::get('/setup', function (Request $request) { 'status' => 200, 'api_key' => $device->api_key, 'friendly_id' => $device->friendly_id, - 'image_url' => url('storage/images/setup-logo.png'), + 'image_url' => url('storage/'.ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo')), 'message' => 'Welcome to TRMNL BYOS', ]); }); @@ -444,8 +459,13 @@ Route::get('/current_screen', function (Request $request) { $image_uuid = $device->current_screen_image; if (! $image_uuid) { - $image_path = 'images/setup-logo.bmp'; - $filename = 'setup-logo.bmp'; + $image_path = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo'); + if (! $image_path) { + // Generate from template if no device-specific image exists + $image_uuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo'); + $image_path = 'images/generated/'.$image_uuid.'.png'; + } + $filename = basename($image_path); } else { // Determine image format based on device settings $preferred_format = 'png'; // Default to PNG for newer firmware diff --git a/storage/app/.gitignore b/storage/app/.gitignore deleted file mode 100644 index fedb287..0000000 --- a/storage/app/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -* -!private/ -!public/ -!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/firmwares/.gitignore similarity index 60% rename from storage/app/public/.gitignore rename to storage/app/public/firmwares/.gitignore index 19a4b22..d6b7ef3 100644 --- a/storage/app/public/.gitignore +++ b/storage/app/public/firmwares/.gitignore @@ -1,3 +1,2 @@ * -!images/ !.gitignore diff --git a/storage/app/public/images/default-screens/.gitkeep b/storage/app/public/images/default-screens/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/storage/app/public/images/default-screens/setup-logo_1024_768_8_90.png b/storage/app/public/images/default-screens/setup-logo_1024_768_8_90.png new file mode 100644 index 0000000..3734da1 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1024_768_8_90.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1200_820_3_0.png b/storage/app/public/images/default-screens/setup-logo_1200_820_3_0.png new file mode 100644 index 0000000..17dcf60 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1200_820_3_0.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1400_840_8_90.png b/storage/app/public/images/default-screens/setup-logo_1400_840_8_90.png new file mode 100644 index 0000000..71ecd65 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1400_840_8_90.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1440_1080_4_90.png b/storage/app/public/images/default-screens/setup-logo_1440_1080_4_90.png new file mode 100644 index 0000000..a350061 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1440_1080_4_90.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1448_1072_8_90.png b/storage/app/public/images/default-screens/setup-logo_1448_1072_8_90.png new file mode 100644 index 0000000..ec22fc8 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1448_1072_8_90.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1600_1200_1_0.png b/storage/app/public/images/default-screens/setup-logo_1600_1200_1_0.png new file mode 100644 index 0000000..f080990 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1600_1200_1_0.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1680_1264_8_90.png b/storage/app/public/images/default-screens/setup-logo_1680_1264_8_90.png new file mode 100644 index 0000000..c3099d5 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1680_1264_8_90.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_1872_1404_8_90.png b/storage/app/public/images/default-screens/setup-logo_1872_1404_8_90.png new file mode 100644 index 0000000..5894ab8 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_1872_1404_8_90.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_800_480_1_0.png b/storage/app/public/images/default-screens/setup-logo_800_480_1_0.png new file mode 100644 index 0000000..1b0d150 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_800_480_1_0.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_800_480_2_0.png b/storage/app/public/images/default-screens/setup-logo_800_480_2_0.png new file mode 100644 index 0000000..031e369 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_800_480_2_0.png differ diff --git a/storage/app/public/images/default-screens/setup-logo_800_600_8_90.png b/storage/app/public/images/default-screens/setup-logo_800_600_8_90.png new file mode 100644 index 0000000..61061e7 Binary files /dev/null and b/storage/app/public/images/default-screens/setup-logo_800_600_8_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_1024_768_8_90.png b/storage/app/public/images/default-screens/sleep_1024_768_8_90.png new file mode 100644 index 0000000..f8763f3 Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1024_768_8_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_1200_820_3_0.png b/storage/app/public/images/default-screens/sleep_1200_820_3_0.png new file mode 100644 index 0000000..287ec0a Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1200_820_3_0.png differ diff --git a/storage/app/public/images/default-screens/sleep_1400_840_8_90.png b/storage/app/public/images/default-screens/sleep_1400_840_8_90.png new file mode 100644 index 0000000..2f2166e Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1400_840_8_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_1440_1080_4_90.png b/storage/app/public/images/default-screens/sleep_1440_1080_4_90.png new file mode 100644 index 0000000..5846cad Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1440_1080_4_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_1448_1072_8_90.png b/storage/app/public/images/default-screens/sleep_1448_1072_8_90.png new file mode 100644 index 0000000..a08c008 Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1448_1072_8_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_1600_1200_1_0.png b/storage/app/public/images/default-screens/sleep_1600_1200_1_0.png new file mode 100644 index 0000000..060da4e Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1600_1200_1_0.png differ diff --git a/storage/app/public/images/default-screens/sleep_1680_1264_8_90.png b/storage/app/public/images/default-screens/sleep_1680_1264_8_90.png new file mode 100644 index 0000000..6509a44 Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1680_1264_8_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_1872_1404_8_90.png b/storage/app/public/images/default-screens/sleep_1872_1404_8_90.png new file mode 100644 index 0000000..792d964 Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_1872_1404_8_90.png differ diff --git a/storage/app/public/images/default-screens/sleep_800_600_8_90.png b/storage/app/public/images/default-screens/sleep_800_600_8_90.png new file mode 100644 index 0000000..f066974 Binary files /dev/null and b/storage/app/public/images/default-screens/sleep_800_600_8_90.png differ diff --git a/storage/app/public/images/setup-logo.png b/storage/app/public/images/setup-logo.png new file mode 100644 index 0000000..1fc342c Binary files /dev/null and b/storage/app/public/images/setup-logo.png differ diff --git a/storage/app/public/images/sleep.bmp b/storage/app/public/images/sleep.bmp new file mode 100644 index 0000000..a55af63 Binary files /dev/null and b/storage/app/public/images/sleep.bmp differ diff --git a/storage/app/public/images/sleep.png b/storage/app/public/images/sleep.png new file mode 100644 index 0000000..49bdabf Binary files /dev/null and b/storage/app/public/images/sleep.png differ diff --git a/tests/Feature/Api/DeviceEndpointsTest.php b/tests/Feature/Api/DeviceEndpointsTest.php index 005e73e..726f313 100644 --- a/tests/Feature/Api/DeviceEndpointsTest.php +++ b/tests/Feature/Api/DeviceEndpointsTest.php @@ -812,10 +812,10 @@ test('device in sleep mode returns sleep image and correct refresh rate', functi 'fw-version' => '1.0.0', ])->get('/api/display'); - $response->assertOk() - ->assertJson([ - 'filename' => 'sleep.png', - ]); + $response->assertOk(); + + // The filename should be a UUID-based PNG file since we're generating from template + expect($response['filename'])->toMatch('/^[a-f0-9-]+\.png$/'); expect($response['refresh_rate'])->toBeGreaterThan(0); Carbon\Carbon::setTestNow(); // Clear test time @@ -867,8 +867,10 @@ test('device returns sleep.png and correct refresh time when paused', function ( $response->assertOk(); $json = $response->json(); - expect($json['filename'])->toBe('sleep.png'); - expect($json['image_url'])->toContain('sleep.png'); + + // The filename should be a UUID-based PNG file since we're generating from template + expect($json['filename'])->toMatch('/^[a-f0-9-]+\.png$/'); + expect($json['image_url'])->toContain('images/generated/'); expect($json['refresh_rate'])->toBeLessThanOrEqual(3600); // ~60 min }); diff --git a/tests/Feature/GenerateDefaultImagesTest.php b/tests/Feature/GenerateDefaultImagesTest.php new file mode 100644 index 0000000..6c084c9 --- /dev/null +++ b/tests/Feature/GenerateDefaultImagesTest.php @@ -0,0 +1,89 @@ +not->toBeEmpty(); + + // Run the command + $this->artisan('images:generate-defaults') + ->assertExitCode(0); + + // Check that the default-screens directory was created + expect(Storage::disk('public')->exists('images/default-screens'))->toBeTrue(); + + // Check that images were generated for each device model + foreach ($deviceModels as $deviceModel) { + $extension = $deviceModel->mime_type === 'image/bmp' ? 'bmp' : 'png'; + $filename = "{$deviceModel->width}_{$deviceModel->height}_{$deviceModel->bit_depth}_{$deviceModel->rotation}.{$extension}"; + + $setupPath = "images/default-screens/setup-logo_{$filename}"; + $sleepPath = "images/default-screens/sleep_{$filename}"; + + expect(Storage::disk('public')->exists($setupPath))->toBeTrue(); + expect(Storage::disk('public')->exists($sleepPath))->toBeTrue(); + } +}); + +test('getDeviceSpecificDefaultImage returns correct path for device with model', function () { + $deviceModel = DeviceModel::first(); + expect($deviceModel)->not->toBeNull(); + + $device = new Device(); + $device->deviceModel = $deviceModel; + + $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo'); + $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep'); + + expect($setupImage)->toContain('images/default-screens/setup-logo_'); + expect($sleepImage)->toContain('images/default-screens/sleep_'); +}); + +test('getDeviceSpecificDefaultImage falls back to original images for device without model', function () { + $device = new Device(); + $device->deviceModel = null; + + $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo'); + $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep'); + + expect($setupImage)->toBe('images/setup-logo.bmp'); + expect($sleepImage)->toBe('images/sleep.bmp'); +}); + +test('generateDefaultScreenImage creates images from Blade templates', function () { + $device = Device::factory()->create(); + + $setupUuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo'); + $sleepUuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep'); + + expect($setupUuid)->not->toBeEmpty(); + expect($sleepUuid)->not->toBeEmpty(); + expect($setupUuid)->not->toBe($sleepUuid); + + // Check that the generated images exist + $setupPath = "images/generated/{$setupUuid}.png"; + $sleepPath = "images/generated/{$sleepUuid}.png"; + + expect(Storage::disk('public')->exists($setupPath))->toBeTrue(); + expect(Storage::disk('public')->exists($sleepPath))->toBeTrue(); +}); + +test('generateDefaultScreenImage throws exception for invalid image type', function () { + $device = Device::factory()->create(); + + expect(fn () => ImageGenerationService::generateDefaultScreenImage($device, 'invalid-type')) + ->toThrow(InvalidArgumentException::class); +}); + +test('getDeviceSpecificDefaultImage returns null for invalid image type', function () { + $device = new Device(); + $device->deviceModel = DeviceModel::first(); + + $result = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'invalid-type'); + expect($result)->toBeNull(); +}); diff --git a/tests/Feature/TransformDefaultImagesTest.php b/tests/Feature/TransformDefaultImagesTest.php new file mode 100644 index 0000000..041c708 --- /dev/null +++ b/tests/Feature/TransformDefaultImagesTest.php @@ -0,0 +1,89 @@ +not->toBeEmpty(); + + // Run the command + $this->artisan('images:generate-defaults') + ->assertExitCode(0); + + // Check that the default-screens directory was created + expect(Storage::disk('public')->exists('images/default-screens'))->toBeTrue(); + + // Check that images were generated for each device model + foreach ($deviceModels as $deviceModel) { + $extension = $deviceModel->mime_type === 'image/bmp' ? 'bmp' : 'png'; + $filename = "{$deviceModel->width}_{$deviceModel->height}_{$deviceModel->bit_depth}_{$deviceModel->rotation}.{$extension}"; + + $setupPath = "images/default-screens/setup-logo_{$filename}"; + $sleepPath = "images/default-screens/sleep_{$filename}"; + + expect(Storage::disk('public')->exists($setupPath))->toBeTrue(); + expect(Storage::disk('public')->exists($sleepPath))->toBeTrue(); + } +}); + +test('getDeviceSpecificDefaultImage returns correct path for device with model', function () { + $deviceModel = DeviceModel::first(); + expect($deviceModel)->not->toBeNull(); + + $device = new Device(); + $device->deviceModel = $deviceModel; + + $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo'); + $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep'); + + expect($setupImage)->toContain('images/default-screens/setup-logo_'); + expect($sleepImage)->toContain('images/default-screens/sleep_'); +}); + +test('getDeviceSpecificDefaultImage falls back to original images for device without model', function () { + $device = new Device(); + $device->deviceModel = null; + + $setupImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'setup-logo'); + $sleepImage = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'sleep'); + + expect($setupImage)->toBe('images/setup-logo.bmp'); + expect($sleepImage)->toBe('images/sleep.bmp'); +}); + +test('generateDefaultScreenImage creates images from Blade templates', function () { + $device = Device::factory()->create(); + + $setupUuid = ImageGenerationService::generateDefaultScreenImage($device, 'setup-logo'); + $sleepUuid = ImageGenerationService::generateDefaultScreenImage($device, 'sleep'); + + expect($setupUuid)->not->toBeEmpty(); + expect($sleepUuid)->not->toBeEmpty(); + expect($setupUuid)->not->toBe($sleepUuid); + + // Check that the generated images exist + $setupPath = "images/generated/{$setupUuid}.png"; + $sleepPath = "images/generated/{$sleepUuid}.png"; + + expect(Storage::disk('public')->exists($setupPath))->toBeTrue(); + expect(Storage::disk('public')->exists($sleepPath))->toBeTrue(); +})->skipOnCI(); + +test('generateDefaultScreenImage throws exception for invalid image type', function () { + $device = Device::factory()->create(); + + expect(fn () => ImageGenerationService::generateDefaultScreenImage($device, 'invalid-type')) + ->toThrow(InvalidArgumentException::class); +}); + +test('getDeviceSpecificDefaultImage returns null for invalid image type', function () { + $device = new Device(); + $device->deviceModel = DeviceModel::first(); + + $result = ImageGenerationService::getDeviceSpecificDefaultImage($device, 'invalid-type'); + expect($result)->toBeNull(); +});