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
111 lines
3.4 KiB
PHP
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();
|
|
});
|