pp-planer/tests/Feature/SongIndexTest.php
Thorsten Bus 27f8402ae8 feat: Wave 4 - Song DB Management + Finalization (T20-T24)
T20: Song DB Page
- Songs/Index.vue with search, action buttons, pagination
- Upload area for .pro files (calls T23 placeholder)
- Song-Datenbank nav link added to AuthenticatedLayout
- Tests: 9 new (44 assertions)

T21: Song DB Edit Popup
- SongEditModal.vue with metadata + ArrangementConfigurator
- Auto-save with fetch (500ms debounce for text, immediate on blur)
- Tests: 11 new (53 assertions)

T22: Song DB Translate Page
- Songs/Translate.vue with two-column editor
- URL fetch or manual paste, line-count constraints
- Group headers with colors, save marks has_translation=true
- Tests: 1 new (12 assertions)

T23: .pro File Placeholders
- ProParserNotImplementedException with HTTP 501
- ProFileController with importPro/downloadPro placeholders
- German error messages
- Tests: 5 new (7 assertions)

T24: Service Finalization + Status
- Two-step finalization with warnings (unmatched songs, missing slides)
- Download placeholder toast
- isReadyToFinalize accessor on Service model
- Tests: 11 new (30 assertions)

All tests passing: 174/174 (905 assertions)
Build: ✓ Vite production build successful
German UI: All user-facing text in German with 'Du' form
2026-03-01 20:30:07 +01:00

111 lines
3.4 KiB
PHP

<?php
use App\Models\Song;
use App\Models\User;
/*
|--------------------------------------------------------------------------
| Song Index Page Tests (Inertia)
|--------------------------------------------------------------------------
*/
beforeEach(function () {
$this->user = User::factory()->create();
$this->withoutVite();
});
test('songs index page renders for authenticated users', function () {
$response = $this->actingAs($this->user)
->get('/songs');
$response->assertOk();
$response->assertInertia(fn ($page) => $page->component('Songs/Index'));
});
test('songs index page redirects unauthenticated users to login', function () {
$response = $this->get('/songs');
$response->assertRedirect('/login');
});
test('songs index route is named songs.index', function () {
$this->actingAs($this->user);
$url = route('songs.index');
expect($url)->toContain('/songs');
});
test('songs api returns data for songs page', function () {
Song::factory()->count(3)->create();
$response = $this->actingAs($this->user)
->getJson('/api/songs');
$response->assertOk()
->assertJsonStructure([
'data' => [['id', 'title', 'ccli_id', 'has_translation', 'created_at', 'updated_at']],
'meta' => ['current_page', 'last_page', 'per_page', 'total'],
]);
expect($response->json('meta.total'))->toBe(3);
});
test('songs api search filters by title', function () {
Song::factory()->create(['title' => 'Großer Gott wir loben dich']);
Song::factory()->create(['title' => 'Amazing Grace']);
$response = $this->actingAs($this->user)
->getJson('/api/songs?search=Großer');
$response->assertOk();
expect($response->json('meta.total'))->toBe(1);
expect($response->json('data.0.title'))->toBe('Großer Gott wir loben dich');
});
test('songs api search filters by ccli id', function () {
Song::factory()->create(['ccli_id' => '7654321', 'title' => 'Song A']);
Song::factory()->create(['ccli_id' => '1234567', 'title' => 'Song B']);
$response = $this->actingAs($this->user)
->getJson('/api/songs?search=7654321');
$response->assertOk();
expect($response->json('meta.total'))->toBe(1);
expect($response->json('data.0.ccli_id'))->toBe('7654321');
});
test('songs api does not return soft-deleted songs', function () {
Song::factory()->count(2)->create();
Song::factory()->create(['deleted_at' => now()]);
$response = $this->actingAs($this->user)
->getJson('/api/songs');
$response->assertOk();
expect($response->json('meta.total'))->toBe(2);
});
test('songs api paginates results', function () {
Song::factory()->count(25)->create();
$response = $this->actingAs($this->user)
->getJson('/api/songs?per_page=10');
$response->assertOk();
expect($response->json('meta.total'))->toBe(25);
expect($response->json('meta.per_page'))->toBe(10);
expect($response->json('meta.last_page'))->toBe(3);
expect(count($response->json('data')))->toBe(10);
});
test('songs api delete soft-deletes a song', function () {
$song = Song::factory()->create(['title' => 'To Delete']);
$response = $this->actingAs($this->user)
->deleteJson("/api/songs/{$song->id}");
$response->assertOk();
expect(Song::find($song->id))->toBeNull();
expect(Song::withTrashed()->find($song->id))->not->toBeNull();
});