- CCLI import: group lyrics into 2-line slides (no blank line per line) - Add-section: searchable label combobox with create-new option - Service edit: show current global key-visual/background default live - Assign dialog: prefill+open search, SongSelect link by CCLI nr/name - "Auf SongSelect suchen" now also opens the CCLI import dialog - SongDB: mark empty songs "Ohne Inhalt", default-on content filter - Translation paste: strip section-mark lines so line mapping holds
140 lines
4.4 KiB
PHP
140 lines
4.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', 'has_content', 'created_at', 'updated_at']],
|
|
'meta' => ['current_page', 'last_page', 'per_page', 'total'],
|
|
]);
|
|
expect($response->json('meta.total'))->toBe(3);
|
|
});
|
|
|
|
test('songs api marks songs without slides as no content', function () {
|
|
$song = Song::factory()->create();
|
|
|
|
$response = $this->actingAs($this->user)
|
|
->getJson('/api/songs');
|
|
|
|
$response->assertOk();
|
|
expect($response->json('data.0.has_content'))->toBeFalse();
|
|
});
|
|
|
|
test('songs api with_content filter hides songs without content', function () {
|
|
$withContent = Song::factory()->create(['title' => 'Mit Inhalt']);
|
|
$section = $withContent->sections()->create([
|
|
'label_id' => \App\Models\Label::factory()->create()->id,
|
|
'order' => 1,
|
|
]);
|
|
$section->slides()->create(['order' => 1, 'text_content' => 'Zeile']);
|
|
|
|
Song::factory()->create(['title' => 'Ohne Inhalt']);
|
|
|
|
$response = $this->actingAs($this->user)
|
|
->getJson('/api/songs?with_content=1');
|
|
|
|
$response->assertOk();
|
|
expect($response->json('meta.total'))->toBe(1);
|
|
expect($response->json('data.0.title'))->toBe('Mit Inhalt');
|
|
expect($response->json('data.0.has_content'))->toBeTrue();
|
|
});
|
|
|
|
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();
|
|
});
|