$name, 'format' => 'JPG', 'width' => 1920, 'height' => 1080, 'bundleRelative' => true]; } /** Generate a one-slide song and run the production builder over it. */ private function build(array $slideData, string $name = 'Test'): ParserSlide { $groups = [['name' => 'Gruppe', 'color' => [0, 0, 0, 1], 'slides' => [$slideData]]]; $arrangements = [['name' => 'normal', 'groupNames' => ['Gruppe']]]; $song = ProFileGenerator::generate($name, $groups, $arrangements); app(SlideMediaElementBuilder::class)->replaceImageActionsWithElements($song); return $song->getSlides()[0]; } public function test_song_slide_keeps_background_action_and_text_without_image_element(): void { $groups = [['name' => 'Gruppe', 'color' => [0, 0, 0, 1], 'slides' => [[ 'text' => 'Textzeile', 'background' => $this->bgData(), ]]]]; $arrangements = [['name' => 'normal', 'groupNames' => ['Gruppe']]]; $song = ProFileGenerator::generate('Song', $groups, $arrangements); // Sanity: the generator emits a BACKGROUND media action before conversion. $this->assertTrue($song->getSlides()[0]->hasBackgroundMedia()); app(SlideMediaElementBuilder::class)->replaceImageActionsWithElements($song); $slide = $song->getSlides()[0]; // Background stays a BACKGROUND media action (NOT converted to an element). $this->assertTrue($slide->hasBackgroundMedia()); $this->assertSame('BACKGROUND.jpg', $slide->getBackgroundMediaUrl()); // Text element preserved; no content fill.media element exists. $this->assertTrue($this->elementHasTextAt($slide, 0)); $this->assertSame([], $this->fillMediaUrls($slide)); // No foreground image action. $this->assertFalse($this->hasForegroundImageMediaAction($slide)); $this->assertFalse($slide->hasMedia()); } public function test_uploaded_content_slide_becomes_full_frame_element_without_foreground_action(): void { // Production uploaded-slide data: media + format + label + bundleRelative (no text). $slide = $this->build([ 'media' => 'UPLOAD.jpg', 'format' => 'JPG', 'label' => 'upload.jpg', 'bundleRelative' => true, ]); // A single full-frame content fill.media element, no text. $this->assertCount(1, $this->baseSlideElements($slide)); $this->assertSame('UPLOAD.jpg', $this->fillMediaUrlAt($slide, 0)); $this->assertTrue($this->isFullFrameAt($slide, 0)); $this->assertFalse($this->elementHasTextAt($slide, 0)); // No foreground media action remains; no background configured at all. $this->assertFalse($this->hasForegroundImageMediaAction($slide)); $this->assertFalse($slide->hasBackgroundMedia()); $this->assertFalse($this->hasImageMediaAction($slide)); } public function test_uploaded_content_slide_with_service_background_keeps_background_action_behind_element(): void { $slide = $this->build([ 'media' => 'UPLOAD.jpg', 'format' => 'JPG', 'label' => 'upload.jpg', 'bundleRelative' => true, 'background' => $this->bgData(), ]); // Uploaded image is a full-frame content element on top. $this->assertSame('UPLOAD.jpg', $this->fillMediaUrlAt($slide, 0)); $this->assertTrue($this->isFullFrameAt($slide, 0)); $this->assertFalse($this->hasForegroundImageMediaAction($slide)); // Service background stays a BACKGROUND media action behind the element. $this->assertTrue($slide->hasBackgroundMedia()); $this->assertSame('BACKGROUND.jpg', $slide->getBackgroundMediaUrl()); } public function test_key_visual_slide_is_background_action_only_without_content_element(): void { // Production key-visual slide data: imageOnly + background. $slide = $this->build([ 'imageOnly' => true, 'background' => $this->bgData('KEY_VISUAL.jpg'), ]); // Key-visual is a BACKGROUND media action, not a content element. $this->assertTrue($slide->hasBackgroundMedia()); $this->assertSame('KEY_VISUAL.jpg', $slide->getBackgroundMediaUrl()); // No content fill.media element and no foreground action. $this->assertSame([], $this->fillMediaUrls($slide)); $this->assertFalse($this->hasForegroundImageMediaAction($slide)); } public function test_name_tag_slide_keeps_text_background_action_and_macro(): void { $slide = $this->build([ 'text' => 'Erika Predigt', 'subtitle' => 'Predigt', 'background' => $this->bgData('KEY_VISUAL.jpg'), 'macro' => [ 'name' => 'Namenseinblender', 'uuid' => '11111111-1111-4111-8111-111111111111', 'collectionName' => '--MAIN--', 'collectionUuid' => null, ], ]); // Text element preserved (name-tag text sits above the background). $this->assertTrue($this->elementHasTextAt($slide, 0)); $this->assertSame([], $this->fillMediaUrls($slide)); // Key-visual behind the name tag stays a BACKGROUND media action. $this->assertTrue($slide->hasBackgroundMedia()); $this->assertSame('KEY_VISUAL.jpg', $slide->getBackgroundMediaUrl()); // Macro action preserved. $this->assertTrue($slide->hasMacro()); $this->assertSame('Namenseinblender', $slide->getMacroName()); // No image content element, no foreground action. $this->assertFalse($this->hasForegroundImageMediaAction($slide)); } public function test_exported_song_pro_roundtrip_keeps_background_action_and_text(): void { Storage::fake('public'); Storage::disk('public')->put('slides/background.jpg', 'background-image'); $service = Service::factory()->create(['background_filename' => 'slides/background.jpg']); $song = $this->createSongWithContent(); $path = app(ProExportService::class)->generateProFile($song, $service); $parserSong = ProFileReader::read($path); @unlink($path); $slides = []; foreach ($parserSong->getGroups() as $group) { foreach ($parserSong->getSlidesForGroup($group) as $slide) { $slides[] = $slide; } } $this->assertNotEmpty($slides); foreach ($slides as $slide) { // Background survives the .pro write+read as a BACKGROUND media action. $this->assertTrue($slide->hasBackgroundMedia()); $this->assertSame('BACKGROUND.jpg', $slide->getBackgroundMediaUrl()); // Text preserved; no content image element; no foreground action. $this->assertTrue($this->elementHasTextAt($slide, 0)); $this->assertNotSame('', $slide->getPlainText()); $this->assertSame([], $this->fillMediaUrls($slide)); $this->assertFalse($this->hasForegroundImageMediaAction($slide)); } } private function createSongWithContent(): Song { $song = Song::create([ 'title' => 'Fill Media Song', 'ccli_id' => '99887', 'author' => 'Test Author', 'copyright_text' => 'Test Publisher', ]); $verse = Label::firstOrCreate(['name' => 'Verse 1 - Fill Media Song'], ['color' => '#2196F3']); $section = $song->sections()->create(['label_id' => $verse->id, 'order' => 0]); $section->slides()->create(['order' => 0, 'text_content' => 'Erste Zeile']); $section->slides()->create(['order' => 1, 'text_content' => 'Zweite Zeile']); $arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]); $arrangement->arrangementSections()->create(['song_section_id' => $section->id, 'order' => 0]); return $song; } }