pp-planer/tests/e2e/song-agenda-link.spec.ts
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

110 lines
3.6 KiB
TypeScript

import { test, expect } from '@playwright/test';
// FIX 6: matched song title in the service agenda links to the SongDB edit modal
// (route('songs.index')#song-<id>); FIX 5: songs whose default arrangement has no
// content slides show a "Keine Inhaltsfolien" indicator.
//
// These tests depend on synced service data. When no editable service / matched song
// exists, they skip gracefully (same pattern as service-edit-songs.spec.ts).
async function navigateToEditPage(page): Promise<boolean> {
await page.goto('/services');
await page.waitForLoadState('networkidle');
const editButton = page.getByTestId('service-list-edit-button').first();
const hasEditableService = await editButton.isVisible().catch(() => false);
if (!hasEditableService) {
return false;
}
await editButton.click();
await page.waitForLoadState('networkidle');
return true;
}
test('matched song title links to SongDB edit hash', async ({ page }) => {
const navigated = await navigateToEditPage(page);
if (!navigated) {
test.skip();
}
// A matched title renders as an Inertia <Link> (an <a> with data-testid).
const titleLink = page.locator('a[data-testid="song-agenda-title"]').first();
const hasMatched = await titleLink.isVisible().catch(() => false);
if (!hasMatched) {
test.skip();
}
const href = await titleLink.getAttribute('href');
expect(href).toBeTruthy();
// route('songs.index') + '#song-<id>'
expect(href).toMatch(/\/songs(\/index)?#song-\d+$/);
});
test('clicking matched song title opens the edit modal on the Songs page', async ({ page }) => {
const navigated = await navigateToEditPage(page);
if (!navigated) {
test.skip();
}
const titleLink = page.locator('a[data-testid="song-agenda-title"]').first();
const hasMatched = await titleLink.isVisible().catch(() => false);
if (!hasMatched) {
test.skip();
}
await titleLink.click();
await page.waitForLoadState('networkidle');
// We land on the Songs page with #song-<id> and the edit modal auto-opens.
expect(new URL(page.url()).pathname).toContain('/songs');
expect(page.url()).toMatch(/#song-\d+$/);
const editModal = page.getByTestId('song-list-edit-modal');
await expect(editModal).toBeVisible({ timeout: 5000 });
});
test('unmatched song title stays plain text (no link)', async ({ page }) => {
const navigated = await navigateToEditPage(page);
if (!navigated) {
test.skip();
}
// An unmatched row has a request-creation button; its title is a <span>, not <a>.
const requestButton = page.getByTestId('song-request-creation').first();
const hasUnmatched = await requestButton.isVisible().catch(() => false);
if (!hasUnmatched) {
test.skip();
}
const unmatchedRow = page
.getByTestId('song-agenda-item')
.filter({ has: page.getByTestId('song-request-creation') })
.first();
const plainTitle = unmatchedRow.locator('span[data-testid="song-agenda-title"]');
await expect(plainTitle).toBeVisible();
await expect(unmatchedRow.locator('a[data-testid="song-agenda-title"]')).toHaveCount(0);
});
test('song without content slides shows "Keine Inhaltsfolien" indicator', async ({ page }) => {
const navigated = await navigateToEditPage(page);
if (!navigated) {
test.skip();
}
const noContent = page.getByTestId('song-no-content').first();
const hasNoContent = await noContent.isVisible().catch(() => false);
if (!hasNoContent) {
test.skip();
}
await expect(noContent).toBeVisible();
await expect(noContent).toContainText('Keine Inhaltsfolien');
});