create([ 'width' => 1024, 'height' => 768, 'colors' => 256, 'bit_depth' => 8, 'scale_factor' => 1.5, 'rotation' => 90, 'mime_type' => 'image/png', 'offset_x' => 10, 'offset_y' => 20, ]); // Create a device with the DeviceModel $device = Device::factory()->create([ 'device_model_id' => $deviceModel->id, ]); // Use reflection to access private method $reflection = new ReflectionClass(ImageGenerationService::class); $method = $reflection->getMethod('getImageSettings'); $settings = $method->invoke(null, $device); // Assert DeviceModel settings are used expect($settings['width'])->toBe(1024); expect($settings['height'])->toBe(768); expect($settings['colors'])->toBe(256); expect($settings['bit_depth'])->toBe(8); expect($settings['scale_factor'])->toBe(1.5); expect($settings['rotation'])->toBe(90); expect($settings['mime_type'])->toBe('image/png'); expect($settings['offset_x'])->toBe(10); expect($settings['offset_y'])->toBe(20); expect($settings['use_model_settings'])->toBe(true); }); it('get_image_settings falls back to device settings when no device model', function (): void { // Create a device without DeviceModel $device = Device::factory()->create([ 'width' => 800, 'height' => 480, 'rotate' => 180, 'image_format' => ImageFormat::PNG_8BIT_GRAYSCALE->value, ]); // Use reflection to access private method $reflection = new ReflectionClass(ImageGenerationService::class); $method = $reflection->getMethod('getImageSettings'); $settings = $method->invoke(null, $device); // Assert device settings are used expect($settings['width'])->toBe(800); expect($settings['height'])->toBe(480); expect($settings['rotation'])->toBe(180); expect($settings['image_format'])->toBe(ImageFormat::PNG_8BIT_GRAYSCALE->value); expect($settings['use_model_settings'])->toBe(false); }); it('get_image_settings uses defaults for missing device properties', function (): void { // Create a device without DeviceModel and missing properties $device = Device::factory()->create([ 'width' => null, 'height' => null, 'rotate' => null, // image_format has a default value of 'auto', so we can't set it to null ]); // Use reflection to access private method $reflection = new ReflectionClass(ImageGenerationService::class); $method = $reflection->getMethod('getImageSettings'); $settings = $method->invoke(null, $device); // Assert default values are used expect($settings['width'])->toBe(800); expect($settings['height'])->toBe(480); expect($settings['rotation'])->toBe(0); expect($settings['colors'])->toBe(2); expect($settings['bit_depth'])->toBe(1); expect($settings['scale_factor'])->toBe(1.0); expect($settings['mime_type'])->toBe('image/png'); expect($settings['offset_x'])->toBe(0); expect($settings['offset_y'])->toBe(0); // image_format defaults to 'auto' when not set expect($settings['image_format'])->toBe('auto'); }); it('determine_image_format_from_model returns correct formats', function (): void { // Use reflection to access private method $reflection = new ReflectionClass(ImageGenerationService::class); $method = $reflection->getMethod('determineImageFormatFromModel'); // Test BMP format $bmpModel = DeviceModel::factory()->create([ 'mime_type' => 'image/bmp', 'bit_depth' => 1, 'colors' => 2, ]); $format = $method->invoke(null, $bmpModel); expect($format)->toBe(ImageFormat::BMP3_1BIT_SRGB->value); // Test PNG 8-bit grayscale format $pngGrayscaleModel = DeviceModel::factory()->create([ 'mime_type' => 'image/png', 'bit_depth' => 8, 'colors' => 2, ]); $format = $method->invoke(null, $pngGrayscaleModel); expect($format)->toBe(ImageFormat::PNG_8BIT_GRAYSCALE->value); // Test PNG 8-bit 256 color format $png256Model = DeviceModel::factory()->create([ 'mime_type' => 'image/png', 'bit_depth' => 8, 'colors' => 256, ]); $format = $method->invoke(null, $png256Model); expect($format)->toBe(ImageFormat::PNG_8BIT_256C->value); // Test PNG 2-bit 4 color format $png4ColorModel = DeviceModel::factory()->create([ 'mime_type' => 'image/png', 'bit_depth' => 2, 'colors' => 4, ]); $format = $method->invoke(null, $png4ColorModel); expect($format)->toBe(ImageFormat::PNG_2BIT_4C->value); // Test unknown format returns AUTO $unknownModel = DeviceModel::factory()->create([ 'mime_type' => 'image/jpeg', 'bit_depth' => 16, 'colors' => 65536, ]); $format = $method->invoke(null, $unknownModel); expect($format)->toBe(ImageFormat::AUTO->value); }); it('cleanup_folder identifies active images correctly', function (): void { // Create devices with images $device1 = Device::factory()->create(['current_screen_image' => 'active-uuid-1']); $device2 = Device::factory()->create(['current_screen_image' => 'active-uuid-2']); $device3 = Device::factory()->create(['current_screen_image' => null]); // Create a plugin with image $plugin = App\Models\Plugin::factory()->create(['current_image' => 'plugin-uuid']); // For unit testing, we could test the logic that determines active UUIDs $activeDeviceImageUuids = Device::pluck('current_screen_image')->filter()->toArray(); $activePluginImageUuids = App\Models\Plugin::pluck('current_image')->filter()->toArray(); $activeImageUuids = array_merge($activeDeviceImageUuids, $activePluginImageUuids); expect($activeImageUuids)->toContain('active-uuid-1'); expect($activeImageUuids)->toContain('active-uuid-2'); expect($activeImageUuids)->toContain('plugin-uuid'); expect($activeImageUuids)->not->toContain(null); }); it('reset_if_not_cacheable detects device models', function (): void { // Create a plugin $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); // Create a device with DeviceModel Device::factory()->create([ 'device_model_id' => DeviceModel::factory()->create()->id, ]); // Test that the method detects DeviceModels and resets cache ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); expect($plugin->current_image)->toBeNull(); }); it('reset_if_not_cacheable detects custom dimensions', function (): void { // Create a plugin $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); // Create a device with custom dimensions Device::factory()->create([ 'width' => 1024, // Different from default 800 'height' => 768, // Different from default 480 ]); // Test that the method detects custom dimensions and resets cache ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); expect($plugin->current_image)->toBeNull(); }); it('reset_if_not_cacheable preserves cache for standard devices', function (): void { // Create a plugin $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); // Create devices with standard dimensions Device::factory()->count(3)->create([ 'width' => 800, 'height' => 480, 'rotate' => 0, ]); // Test that the method preserves cache for standard devices ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); expect($plugin->current_image)->toBe('test-uuid'); }); it('reset_if_not_cacheable preserves cache for og_png and og_plus device models', function (): void { // Create a plugin $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); // Create og_png device model $ogPngModel = DeviceModel::factory()->create([ 'name' => 'test_og_png', 'width' => 800, 'height' => 480, 'rotation' => 0, ]); // Create og_plus device model $ogPlusModel = DeviceModel::factory()->create([ 'name' => 'test_og_plus', 'width' => 800, 'height' => 480, 'rotation' => 0, ]); // Create devices with og_png and og_plus device models Device::factory()->create(['device_model_id' => $ogPngModel->id]); Device::factory()->create(['device_model_id' => $ogPlusModel->id]); // Test that the method preserves cache for standard device models ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); expect($plugin->current_image)->toBe('test-uuid'); }); it('reset_if_not_cacheable resets cache for non-standard device models', function (): void { // Create a plugin $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); // Create a non-standard device model (e.g., kindle) $kindleModel = DeviceModel::factory()->create([ 'name' => 'test_amazon_kindle_2024', 'width' => 1400, 'height' => 840, 'rotation' => 90, ]); // Create a device with the non-standard device model Device::factory()->create(['device_model_id' => $kindleModel->id]); // Test that the method resets cache for non-standard device models ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); expect($plugin->current_image)->toBeNull(); }); it('reset_if_not_cacheable handles null plugin', function (): void { // Test that the method handles null plugin gracefully expect(fn () => ImageGenerationService::resetIfNotCacheable(null))->not->toThrow(Exception::class); }); it('image_format enum includes new 2bit 4c format', function (): void { // Test that the new format is properly defined in the enum expect(ImageFormat::PNG_2BIT_4C->value)->toBe('png_2bit_4c'); expect(ImageFormat::PNG_2BIT_4C->label())->toBe('PNG 2-bit Grayscale 4c'); }); it('device model relationship works correctly', function (): void { // Create a DeviceModel $deviceModel = DeviceModel::factory()->create(); // Create a device with the DeviceModel $device = Device::factory()->create([ 'device_model_id' => $deviceModel->id, ]); // Test the relationship expect($device->deviceModel)->toBeInstanceOf(DeviceModel::class); expect($device->deviceModel->id)->toBe($deviceModel->id); }); it('device without device model returns null relationship', function (): void { // Create a device without DeviceModel $device = Device::factory()->create([ 'device_model_id' => null, ]); // Test the relationship returns null expect($device->deviceModel)->toBeNull(); });