pp-planer/tests/Feature/ServiceSongContentSlidesTest.php
Thorsten Bus 42b8b5f428 fix(export): copyright/blank slides, bundle-relative media, probundle injection, song links
- 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"
2026-06-21 09:58:55 +02:00

185 lines
5.7 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Service;
use App\Models\ServiceSong;
use App\Models\Song;
use App\Models\SongArrangement;
use App\Models\SongArrangementSection;
use App\Models\SongSection;
use App\Models\SongSlide;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ServiceSongContentSlidesTest extends TestCase
{
use RefreshDatabase;
private function makeServiceWithSong(callable $buildSong): array
{
$service = Service::factory()->create([
'date' => Carbon::today(),
'finalized_at' => null,
]);
$song = Song::factory()->create();
$arrangement = $buildSong($song);
$serviceSong = ServiceSong::factory()->create([
'service_id' => $service->id,
'song_id' => $song->id,
'song_arrangement_id' => $arrangement->id,
'order' => 1,
]);
return [$service, $serviceSong];
}
private function defaultArrangementWithContentSlide(Song $song): SongArrangement
{
$arrangement = SongArrangement::factory()->create([
'song_id' => $song->id,
'is_default' => true,
]);
$section = SongSection::factory()->create([
'song_id' => $song->id,
'locked' => false,
]);
SongSlide::factory()->create(['song_section_id' => $section->id]);
SongArrangementSection::factory()->create([
'song_arrangement_id' => $arrangement->id,
'song_section_id' => $section->id,
'order' => 0,
]);
return $arrangement;
}
public function test_has_content_slides_ist_true_wenn_standard_arrangement_inhaltsfolie_hat(): void
{
Carbon::setTestNow('2026-03-01 10:00:00');
$this->withoutVite();
[$service, $serviceSong] = $this->makeServiceWithSong(
fn (Song $song) => $this->defaultArrangementWithContentSlide($song)
);
$response = $this->actingAs(User::factory()->create())
->get(route('services.edit', $service->id));
$response->assertInertia(
fn ($page) => $page
->component('Services/Edit')
->where('serviceSongs.0.has_content_slides', true)
);
}
public function test_has_content_slides_ist_false_wenn_section_gesperrt(): void
{
Carbon::setTestNow('2026-03-01 10:00:00');
$this->withoutVite();
[$service, $serviceSong] = $this->makeServiceWithSong(function (Song $song): SongArrangement {
$arrangement = SongArrangement::factory()->create([
'song_id' => $song->id,
'is_default' => true,
]);
// Locked section with slides -> not a content slide.
$section = SongSection::factory()->create([
'song_id' => $song->id,
'locked' => true,
]);
SongSlide::factory()->create(['song_section_id' => $section->id]);
SongArrangementSection::factory()->create([
'song_arrangement_id' => $arrangement->id,
'song_section_id' => $section->id,
'order' => 0,
]);
return $arrangement;
});
$response = $this->actingAs(User::factory()->create())
->get(route('services.edit', $service->id));
$response->assertInertia(
fn ($page) => $page
->component('Services/Edit')
->where('serviceSongs.0.has_content_slides', false)
);
}
public function test_has_content_slides_ist_false_wenn_section_keine_folien_hat(): void
{
Carbon::setTestNow('2026-03-01 10:00:00');
$this->withoutVite();
[$service, $serviceSong] = $this->makeServiceWithSong(function (Song $song): SongArrangement {
$arrangement = SongArrangement::factory()->create([
'song_id' => $song->id,
'is_default' => true,
]);
// Non-locked section but NO slides -> not a content slide.
$section = SongSection::factory()->create([
'song_id' => $song->id,
'locked' => false,
]);
SongArrangementSection::factory()->create([
'song_arrangement_id' => $arrangement->id,
'song_section_id' => $section->id,
'order' => 0,
]);
return $arrangement;
});
$response = $this->actingAs(User::factory()->create())
->get(route('services.edit', $service->id));
$response->assertInertia(
fn ($page) => $page
->component('Services/Edit')
->where('serviceSongs.0.has_content_slides', false)
);
}
public function test_has_content_slides_ist_null_wenn_kein_song_zugeordnet(): void
{
Carbon::setTestNow('2026-03-01 10:00:00');
$this->withoutVite();
$service = Service::factory()->create([
'date' => Carbon::today(),
'finalized_at' => null,
]);
ServiceSong::create([
'service_id' => $service->id,
'song_id' => null,
'song_arrangement_id' => null,
'use_translation' => false,
'order' => 1,
'cts_song_name' => 'Unmatched Song',
'cts_ccli_id' => '999999',
]);
$response = $this->actingAs(User::factory()->create())
->get(route('services.edit', $service->id));
$response->assertInertia(
fn ($page) => $page
->component('Services/Edit')
->where('serviceSongs.0.has_content_slides', null)
);
}
}