- populate COPYRIGHT (title/author/copyright/CCLI) + blank slides on every song; songHasContent ignores locked sections - foreground info/moderation images now bundle-relative (fixes blank images) - pre-added .probundle injection: Zip64-fix + verbatim .pro extraction (fixes empty bundle) - nametag subtitle split (text + subtitle); smaller non-bold render - skip songs with no content slides at export with German warning - link service agenda songs to SongDB edit modal via #song-<id> - allow CCLI import of metadata-only songs (no lyric sections) - expose has_content_slides on service songs; show "Keine Inhaltsfolien"
155 lines
5 KiB
PHP
155 lines
5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Service;
|
|
use App\Models\Slide;
|
|
use App\Services\PlaylistExportService;
|
|
use App\Services\ProBundleExportService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use ProPresenter\Parser\ProBundleReader;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* FIX 2: Foreground image slide data (information/moderation/sermon blocks) must
|
|
* carry 'bundleRelative' => 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<int, 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);
|
|
}
|
|
}
|