- Mediathek: Video-Vorschaubilder statt Icons (FFmpeg-Thumbnails + Backfill-Command), Kategorie "Sonstiges" - B2in Media-Picker zeigt alle Medientypen, Typ wird automatisch erkannt; Thumbnail-Preview vor allen Medien-URL-Feldern - B2in Marke/Footer: Footer ein/aus, Logo+Claim frei positionierbar (Ecken) mit Constraints, separate Anzeige-Schalter - Angebote-Modul dynamisch: kein Slide-Typ mehr, einheitliches Detail-Layout mit ein-/ausblendbaren Bloecken, Logo/Brand pro Slide, Streichpreis-Option - Player: leere Module stoppen Endlosschleife, dynamische Layout-Anpassung bei verstecktem Footer/Header - Fix: Script-Ladereihenfolge (Livewire vor Flux), entfernte stale public/flux/flux.js, Modal-Crash beim Aktualisieren behoben Co-authored-by: Cursor <cursoragent@cursor.com>
418 lines
16 KiB
PHP
418 lines
16 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Admin\Cms\DisplayList;
|
|
use App\Models\Display;
|
|
use App\Models\DisplayPlaylist;
|
|
use App\Models\DisplayPlaylistItem;
|
|
use App\Models\DisplayVersion;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\URL;
|
|
use Livewire\Livewire;
|
|
|
|
beforeEach(function () {
|
|
$portalDomain = config('domains.domain_portal');
|
|
Livewire::withoutLazyLoading();
|
|
URL::forceRootUrl('https://'.$portalDomain);
|
|
});
|
|
|
|
function createDisplayListPlaylist(Display $display, string $status, array $versionIds): DisplayPlaylist
|
|
{
|
|
$playlist = DisplayPlaylist::factory()
|
|
->state(['status' => $status])
|
|
->create([
|
|
'display_id' => $display->id,
|
|
'published_at' => $status === DisplayPlaylist::STATUS_PUBLISHED ? now() : null,
|
|
]);
|
|
|
|
foreach (array_values($versionIds) as $sortOrder => $versionId) {
|
|
DisplayPlaylistItem::factory()->create([
|
|
'display_playlist_id' => $playlist->id,
|
|
'display_version_id' => $versionId,
|
|
'sort_order' => $sortOrder,
|
|
]);
|
|
}
|
|
|
|
return $playlist;
|
|
}
|
|
|
|
test('display list requires authentication', function () {
|
|
$response = $this->get(route('admin.cms.displays'));
|
|
|
|
$response->assertRedirect('/login');
|
|
});
|
|
|
|
test('display list renders for authenticated users', function () {
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user);
|
|
|
|
$response = $this->get(route('admin.cms.displays'));
|
|
|
|
$response->assertSuccessful();
|
|
$response->assertSeeLivewire(DisplayList::class);
|
|
});
|
|
|
|
test('can create a display', function () {
|
|
$user = User::factory()->create();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal')
|
|
->set('displayName', 'Display 1 - Eingang')
|
|
->set('displayLocation', 'Schaufenster links')
|
|
->call('save');
|
|
|
|
expect(Display::where('name', 'Display 1 - Eingang')->exists())->toBeTrue();
|
|
});
|
|
|
|
test('can assign versions to a display', function () {
|
|
$user = User::factory()->create();
|
|
$version1 = DisplayVersion::factory()->create();
|
|
$version2 = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id)
|
|
->call('addVersion', $version1->id)
|
|
->call('addVersion', $version2->id)
|
|
->call('save');
|
|
|
|
$display->refresh();
|
|
expect($display->livePlaylist->modules->pluck('id')->all())->toBe([$version1->id, $version2->id]);
|
|
});
|
|
|
|
test('can reorder versions in playlist', function () {
|
|
$user = User::factory()->create();
|
|
$version1 = DisplayVersion::factory()->create();
|
|
$version2 = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id)
|
|
->call('addVersion', $version1->id)
|
|
->call('addVersion', $version2->id)
|
|
->call('moveVersion', 1, 'up')
|
|
->call('save');
|
|
|
|
$display->refresh();
|
|
expect($display->livePlaylist->modules->pluck('id')->all())->toBe([$version2->id, $version1->id]);
|
|
});
|
|
|
|
test('can remove version from playlist', function () {
|
|
$user = User::factory()->create();
|
|
$version1 = DisplayVersion::factory()->create();
|
|
$version2 = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$version1->id, $version2->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id)
|
|
->call('removeVersion', 0)
|
|
->call('save');
|
|
|
|
$display->refresh();
|
|
expect($display->livePlaylist->modules->pluck('id')->all())->toBe([$version2->id]);
|
|
});
|
|
|
|
test('can delete a display', function () {
|
|
$user = User::factory()->create();
|
|
$display = Display::factory()->create();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('deleteDisplay', $display->id);
|
|
|
|
expect(Display::find($display->id))->toBeNull();
|
|
});
|
|
|
|
test('can toggle display active status', function () {
|
|
$user = User::factory()->create();
|
|
$display = Display::factory()->create(['is_active' => true]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('toggleActive', $display->id);
|
|
|
|
expect($display->fresh()->is_active)->toBeFalse();
|
|
});
|
|
|
|
test('validates required fields when creating display', function () {
|
|
$user = User::factory()->create();
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal')
|
|
->set('displayName', '')
|
|
->call('save')
|
|
->assertHasErrors(['displayName']);
|
|
});
|
|
|
|
test('does not add duplicate version to playlist', function () {
|
|
$user = User::factory()->create();
|
|
$version = DisplayVersion::factory()->create();
|
|
|
|
$component = Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal')
|
|
->call('addVersion', $version->id)
|
|
->call('addVersion', $version->id);
|
|
|
|
expect($component->get('selectedVersionIds'))->toHaveCount(1);
|
|
});
|
|
|
|
test('plus button adds the first available module when select has no explicit value', function () {
|
|
$user = User::factory()->create();
|
|
$firstVersion = DisplayVersion::factory()->create(['name' => 'Angebote Schauraum']);
|
|
DisplayVersion::factory()->create(['name' => 'Schaufenster Video']);
|
|
|
|
$component = Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal')
|
|
->call('addVersion');
|
|
|
|
expect($component->get('selectedVersionIds'))->toBe([$firstVersion->id]);
|
|
});
|
|
|
|
test('can create a draft playlist from live modules', function () {
|
|
$user = User::factory()->create();
|
|
$version1 = DisplayVersion::factory()->create();
|
|
$version2 = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create(['preview_token' => null]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$version1->id, $version2->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('createDraft', $display->id);
|
|
|
|
$display->refresh();
|
|
|
|
expect($display->preview_token)->not->toBeNull();
|
|
expect($display->draftPlaylist)->not->toBeNull();
|
|
expect($display->draftPlaylist->modules->pluck('id')->all())->toBe([$version1->id, $version2->id]);
|
|
});
|
|
|
|
test('can discard a draft playlist', function () {
|
|
$user = User::factory()->create();
|
|
$version = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create(['preview_token' => 'token-discard-123456789012345678901234']);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$version->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('discardDraft', $display->id);
|
|
|
|
expect($display->fresh()->draftPlaylist)->toBeNull();
|
|
expect($display->fresh()->preview_token)->toBeNull();
|
|
});
|
|
|
|
test('publishing a draft clears the preview token', function () {
|
|
$user = User::factory()->create();
|
|
$liveVersion = DisplayVersion::factory()->create();
|
|
$draftVersion = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create(['preview_token' => 'token-publish-12345678901234567890123456']);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$liveVersion->id]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('publishDraft', $display->id);
|
|
|
|
expect($display->fresh()->preview_token)->toBeNull();
|
|
});
|
|
|
|
test('can rotate the preview token of a draft', function () {
|
|
$user = User::factory()->create();
|
|
$version = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create(['preview_token' => 'token-old-1234567890123456789012345678901']);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$version->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('rotatePreviewToken', $display->id);
|
|
|
|
$newToken = $display->fresh()->preview_token;
|
|
|
|
expect($newToken)->not->toBeNull()
|
|
->and($newToken)->not->toBe('token-old-1234567890123456789012345678901');
|
|
});
|
|
|
|
test('can add multiple modules to a playlist at once', function () {
|
|
$user = User::factory()->create();
|
|
$version1 = DisplayVersion::factory()->create();
|
|
$version2 = DisplayVersion::factory()->create();
|
|
$version3 = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, []);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_PUBLISHED)
|
|
->set('versionsToAdd', [$version1->id, $version3->id])
|
|
->call('addSelectedVersions')
|
|
->assertSet('selectedVersionIds', [$version1->id, $version3->id])
|
|
->assertSet('versionsToAdd', []);
|
|
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
test('can publish a draft playlist over the live playlist', function () {
|
|
$user = User::factory()->create();
|
|
$liveVersion = DisplayVersion::factory()->create(['name' => 'Live Modul']);
|
|
$draftVersion = DisplayVersion::factory()->create(['name' => 'Draft Modul']);
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$liveVersion->id]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('publishDraft', $display->id);
|
|
|
|
$display->refresh();
|
|
|
|
expect($display->draftPlaylist)->toBeNull();
|
|
expect($display->livePlaylist->modules->pluck('id')->all())->toBe([$draftVersion->id]);
|
|
});
|
|
|
|
test('can edit live playlist without changing draft playlist', function () {
|
|
$user = User::factory()->create();
|
|
$liveVersion = DisplayVersion::factory()->create();
|
|
$draftVersion = DisplayVersion::factory()->create();
|
|
$newLiveVersion = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$liveVersion->id]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_PUBLISHED)
|
|
->call('addVersion', $newLiveVersion->id)
|
|
->call('save');
|
|
|
|
$display->refresh();
|
|
|
|
expect($display->livePlaylist->modules->pluck('id')->all())->toBe([$liveVersion->id, $newLiveVersion->id]);
|
|
expect($display->draftPlaylist->modules->pluck('id')->all())->toBe([$draftVersion->id]);
|
|
});
|
|
|
|
test('can edit draft playlist without changing live playlist', function () {
|
|
$user = User::factory()->create();
|
|
$liveVersion = DisplayVersion::factory()->create();
|
|
$draftVersion = DisplayVersion::factory()->create();
|
|
$newDraftVersion = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create(['preview_token' => null]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$liveVersion->id]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_DRAFT)
|
|
->call('addVersion', $newDraftVersion->id)
|
|
->call('save');
|
|
|
|
$display->refresh();
|
|
|
|
expect($display->preview_token)->not->toBeNull();
|
|
expect($display->livePlaylist->modules->pluck('id')->all())->toBe([$liveVersion->id]);
|
|
expect($display->draftPlaylist->modules->pluck('id')->all())->toBe([$draftVersion->id, $newDraftVersion->id]);
|
|
});
|
|
|
|
test('draft editor renders iframe preview url', function () {
|
|
$user = User::factory()->create();
|
|
$draftVersion = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create(['preview_token' => null]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_DRAFT)
|
|
->assertSee('Live-Vorschau')
|
|
->assertSee('/preview/'.$display->fresh()->preview_token, false)
|
|
->assertSeeHtml('<iframe');
|
|
});
|
|
|
|
test('draft editor persists module changes immediately for preview reloads', function () {
|
|
$user = User::factory()->create();
|
|
$draftVersion = DisplayVersion::factory()->create();
|
|
$newDraftVersion = DisplayVersion::factory()->create();
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
$component = Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_DRAFT);
|
|
|
|
$initialRefreshCounter = $component->get('previewFrameRefreshCounter');
|
|
|
|
$component->call('addVersion', $newDraftVersion->id);
|
|
|
|
$display->refresh();
|
|
|
|
expect($display->draftPlaylist->modules->pluck('id')->all())->toBe([$draftVersion->id, $newDraftVersion->id]);
|
|
expect($component->get('previewFrameRefreshCounter'))->toBeGreaterThan($initialRefreshCounter);
|
|
});
|
|
|
|
test('module select only shows modules that can still be added', function () {
|
|
$user = User::factory()->create();
|
|
$selectedVersion = DisplayVersion::factory()->create(['name' => 'Schon gewählt']);
|
|
$availableVersion = DisplayVersion::factory()->create(['name' => 'Noch verfügbar']);
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$selectedVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_PUBLISHED)
|
|
->assertDontSee('Schon gewählt (')
|
|
->assertSee('Noch verfügbar (');
|
|
});
|
|
|
|
test('module select is replaced by a hint when all modules are already selected', function () {
|
|
$user = User::factory()->create();
|
|
$selectedVersion = DisplayVersion::factory()->create(['name' => 'Einziges Modul']);
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$selectedVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->call('openModal', $display->id, DisplayPlaylist::STATUS_PUBLISHED)
|
|
->assertSee('Alle verfügbaren Module sind bereits hinzugefügt.')
|
|
->assertDontSeeHtml('<option value="'.$selectedVersion->id.'">Einziges Modul');
|
|
});
|
|
|
|
test('renders live and draft playlist columns', function () {
|
|
$user = User::factory()->create();
|
|
$liveVersion = DisplayVersion::factory()->create(['name' => 'Live Modul']);
|
|
$draftVersion = DisplayVersion::factory()->create(['name' => 'Draft Modul']);
|
|
$display = Display::factory()->test()->create(['name' => 'Test-Display']);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$liveVersion->id]);
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_DRAFT, [$draftVersion->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->assertSee('Live Modul')
|
|
->assertSee('Draft Modul')
|
|
->assertSee('Öffentliche Display-Übersicht')
|
|
->assertSee('Display-Übersicht öffnen')
|
|
->assertSee('https://cabinet.b2in.eu/display/', false)
|
|
->assertSee('Live bearbeiten')
|
|
->assertSee('Live-URL zum Kopieren')
|
|
->assertSee('https://cabinet.b2in.eu/display/?id='.$display->id, false)
|
|
->assertSee('Entwurf bearbeiten')
|
|
->assertSee('Test-Display');
|
|
});
|
|
|
|
test('display live urls fall back to cabinet domain when config is empty', function () {
|
|
config(['display.player_url' => '']);
|
|
|
|
$user = User::factory()->create();
|
|
$version = DisplayVersion::factory()->create(['name' => 'Live Modul']);
|
|
$display = Display::factory()->create();
|
|
createDisplayListPlaylist($display, DisplayPlaylist::STATUS_PUBLISHED, [$version->id]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(DisplayList::class)
|
|
->assertSee('https://cabinet.b2in.eu/display/', false)
|
|
->assertSee('https://cabinet.b2in.eu/display/?id='.$display->id, false);
|
|
});
|