true so the .pro references the embedded image by a * bundle-relative resource URL instead of an absolute path. Otherwise the slide * renders blank on the presenter PC. */ final class ExportBundleRelativeMediaTest extends TestCase { use RefreshDatabase; protected function setUp(): void { parent::setUp(); Storage::fake('public'); } private function createSlideFile(string $storedFilename): void { $image = imagecreatetruecolor(1920, 1080); ob_start(); imagejpeg($image); $contents = ob_get_clean(); imagedestroy($image); Storage::disk('public')->put($storedFilename, $contents); } private function createSlide(array $attributes): Slide { return Slide::create(array_merge([ 'thumbnail_filename' => 'thumb.jpg', 'uploaded_at' => now()->subDay(), ], $attributes)); } public function test_playlist_export_setzt_bundle_relative_auf_info_folien_slide_daten(): void { $service = Service::factory()->create(['finalized_at' => now(), 'date' => now()]); $this->createSlideFile('slides/info1.jpg'); $this->createSlide([ 'type' => 'information', 'service_id' => null, 'original_filename' => 'info1.jpg', 'stored_filename' => 'slides/info1.jpg', 'sort_order' => 1, ]); $capturedGroups = []; $exportService = new class($capturedGroups) extends PlaylistExportService { /** @var array */ private array $captured; public function __construct(array &$captured) { $this->captured = &$captured; } protected function writeProFile(string $path, string $name, array $groups, array $arrangements): void { $this->captured[] = $groups; file_put_contents($path, 'mock-pro:'.$name); } protected function writePlaylistFile(string $path, string $name, array $items, array $embeddedFiles): void { file_put_contents($path, 'mock-playlist:'.$name); } }; $result = $exportService->generatePlaylist($service); // Find the slide data array carrying our image media. $found = false; foreach ($capturedGroups as $groups) { foreach ($groups as $group) { foreach ($group['slides'] ?? [] as $slideData) { if (isset($slideData['media'])) { $found = true; $this->assertArrayHasKey('bundleRelative', $slideData); $this->assertTrue($slideData['bundleRelative']); } } } } $this->assertTrue($found, 'Expected at least one media slide in captured pro groups.'); $this->cleanupTempDir($result['temp_dir']); } public function test_probundle_export_referenziert_vordergrund_medien_bundle_relativ(): void { $service = Service::factory()->create(['finalized_at' => now(), 'date' => now()]); $this->createSlideFile('slides/mod1.jpg'); $this->createSlide([ 'type' => 'moderation', 'service_id' => $service->id, 'original_filename' => 'mod1.jpg', 'stored_filename' => 'slides/mod1.jpg', 'sort_order' => 1, ]); $bundlePath = app(ProBundleExportService::class)->generateBundle($service, 'moderation'); $this->assertFileExists($bundlePath); $bundle = ProBundleReader::read($bundlePath); $slides = $bundle->getSong()->getSlides(); $mediaSlides = array_filter($slides, fn ($slide) => $slide->hasMedia()); $this->assertNotEmpty($mediaSlides, 'Expected a foreground-media slide in the bundle.'); foreach ($mediaSlides as $slide) { $url = $slide->getMediaUrl(); $this->assertNotNull($url); // Bundle-relative resources use a bare basename (no absolute path / no slash). $this->assertStringNotContainsString('/', $url, 'Foreground media URL must be bundle-relative (basename only).'); } @unlink($bundlePath); } private function cleanupTempDir(string $dir): void { if (! is_dir($dir)) { return; } foreach (scandir($dir) as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir.'/'.$item; is_dir($path) ? $this->cleanupTempDir($path) : unlink($path); } rmdir($dir); } }