diff --git a/tests/e2e/service-edit-agenda.spec.ts b/tests/e2e/service-edit-agenda.spec.ts new file mode 100644 index 0000000..9255b13 --- /dev/null +++ b/tests/e2e/service-edit-agenda.spec.ts @@ -0,0 +1,299 @@ +import { test, expect } from '@playwright/test'; + +async function navigateToEditPage(page) { + 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('edit seite zeigt ablauf sektion statt accordion bloecke', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + await expect(page).toHaveURL(/.*services\/\d+\/edit/); + + const agendaSection = page.getByTestId('agenda-section'); + const emptyState = page.getByText('Keine Ablauf-Elemente vorhanden'); + const hasAgenda = await agendaSection.isVisible().catch(() => false); + const hasEmptyState = await emptyState.isVisible().catch(() => false); + + expect(hasAgenda || hasEmptyState).toBe(true); + + const blockToggles = page.getByTestId('service-edit-block-toggle'); + const toggleCount = await blockToggles.count(); + expect(toggleCount).toBe(0); +}); + +test('informations block ist oben sichtbar', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const informationBlock = page.getByTestId('information-block'); + await expect(informationBlock).toBeVisible(); +}); + +test('ablauf ueberschrift ist sichtbar', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + await expect(page.getByText('Ablauf')).toBeVisible(); +}); + +test('agenda items zeigen korrekte elemente oder empty state', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const agendaSection = page.getByTestId('agenda-section'); + const emptyState = page.getByText('Keine Ablauf-Elemente vorhanden'); + + const hasAgenda = await agendaSection.isVisible().catch(() => false); + const hasEmptyState = await emptyState.isVisible().catch(() => false); + + if (hasEmptyState) { + await expect(page.getByText('Bitte synchronisiere die Daten zuerst')).toBeVisible(); + return; + } + + expect(hasAgenda).toBe(true); + + const agendaRows = page.getByTestId('agenda-item-row'); + const songItems = page.getByTestId('song-agenda-item'); + const headerItems = page.getByTestId('agenda-header-item'); + + const rowCount = await agendaRows.count(); + const songCount = await songItems.count(); + const headerCount = await headerItems.count(); + + expect(rowCount + songCount + headerCount).toBeGreaterThan(0); +}); + +test('header items zeigen titel als ueberschrift', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const headerItems = page.getByTestId('agenda-header-item'); + const headerCount = await headerItems.count(); + + if (headerCount === 0) { + test.skip(); + } + + const firstHeader = headerItems.first(); + await expect(firstHeader).toBeVisible(); + const headerText = await firstHeader.textContent(); + expect(headerText?.trim().length).toBeGreaterThan(0); +}); + +test('song agenda items zeigen songtitel', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const songItems = page.getByTestId('song-agenda-item'); + const songCount = await songItems.count(); + + if (songCount === 0) { + test.skip(); + } + + const firstSong = songItems.first(); + await expect(firstSong).toBeVisible(); + + const songTitle = firstSong.getByTestId('song-agenda-title'); + await expect(songTitle).toBeVisible(); + const titleText = await songTitle.textContent(); + expect(titleText?.trim().length).toBeGreaterThan(0); +}); + +test('song agenda items zeigen arrangement pill wenn zugeordnet', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const arrangementPills = page.getByTestId('arrangement-pill'); + const pillCount = await arrangementPills.count(); + + if (pillCount === 0) { + test.skip(); + } + + const firstPill = arrangementPills.first(); + await expect(firstPill).toBeVisible(); +}); + +test('song agenda item zeigt arrangement bearbeiten button', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const editArrangementBtn = page.getByTestId('song-edit-arrangement'); + const hasBtn = await editArrangementBtn.first().isVisible().catch(() => false); + + if (!hasBtn) { + test.skip(); + } + + await expect(editArrangementBtn.first()).toBeVisible(); +}); + +test('generische agenda items zeigen titel', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const agendaRows = page.getByTestId('agenda-item-row'); + const rowCount = await agendaRows.count(); + + if (rowCount === 0) { + test.skip(); + } + + const firstRow = agendaRows.first(); + await expect(firstRow).toBeVisible(); + + const itemTitle = firstRow.getByTestId('agenda-item-title'); + await expect(itemTitle).toBeVisible(); + expect(await itemTitle.textContent()).toBeTruthy(); +}); + +test('nicht zugeordnete songs zeigen erstellung anfragen button', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const requestBtn = page.getByTestId('song-request-creation'); + const hasUnmatched = await requestBtn.first().isVisible().catch(() => false); + + if (!hasUnmatched) { + test.skip(); + } + + await expect(requestBtn.first()).toBeVisible(); +}); + +test('song suche und manuelle zuordnung sichtbar bei nicht zugeordnetem song', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const searchInput = page.getByTestId('song-search-input'); + const hasSearch = await searchInput.first().isVisible().catch(() => false); + + if (!hasSearch) { + test.skip(); + } + + await expect(searchInput.first()).toBeVisible(); + + const assignBtn = page.getByTestId('song-assign-button'); + await expect(assignBtn.first()).toBeVisible(); +}); + +test('uebersetzungs checkbox sichtbar bei songs mit uebersetzung', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const translationCheckbox = page.getByTestId('song-translation-checkbox'); + const hasTranslation = await translationCheckbox.first().isVisible().catch(() => false); + + if (!hasTranslation) { + test.skip(); + } + + const initialState = await translationCheckbox.first().isChecked(); + + await translationCheckbox.first().click(); + await page.waitForTimeout(300); + + const toggledState = await translationCheckbox.first().isChecked(); + expect(toggledState).not.toBe(initialState); + + await translationCheckbox.first().click(); + await page.waitForTimeout(300); + + const restoredState = await translationCheckbox.first().isChecked(); + expect(restoredState).toBe(initialState); +}); + +test('sticky action bar ist sichtbar', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const actionBar = page.getByTestId('service-edit-action-bar'); + await expect(actionBar).toBeVisible(); + + const inProgress = page.getByText('In Bearbeitung'); + const finalized = page.getByText('Abgeschlossen'); + + const hasInProgress = await inProgress.isVisible().catch(() => false); + const hasFinalized = await finalized.isVisible().catch(() => false); + + expect(hasInProgress || hasFinalized).toBe(true); +}); + +test('abschliessen button in action bar sichtbar', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const finalizeBtn = page.getByTestId('service-edit-finalize-button'); + const hasFinalizeBtn = await finalizeBtn.isVisible().catch(() => false); + + if (!hasFinalizeBtn) { + const reopenBtn = page.getByTestId('service-edit-reopen-button'); + await expect(reopenBtn).toBeVisible(); + return; + } + + await expect(finalizeBtn).toBeVisible(); + await expect(finalizeBtn).toContainText('Abschließen'); + + const finalizeDownloadBtn = page.getByTestId('service-edit-finalize-download-button'); + await expect(finalizeDownloadBtn).toBeVisible(); +}); + +test('zurueck button navigiert zur service liste', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const backBtn = page.getByTestId('service-edit-back-icon-button'); + await expect(backBtn).toBeVisible(); + + await backBtn.click(); + await page.waitForLoadState('networkidle'); + + await expect(page).toHaveURL(/.*services$/); +}); diff --git a/tests/e2e/service-edit-information.spec.ts b/tests/e2e/service-edit-information.spec.ts index 4a28881..a54111a 100644 --- a/tests/e2e/service-edit-information.spec.ts +++ b/tests/e2e/service-edit-information.spec.ts @@ -25,12 +25,10 @@ test('navigate to first editable service edit page', async ({ page }) => { await expect(informationBlock).toBeVisible(); }); -// Test 2: Information block accordion is visible and can be expanded/collapsed -test('information block accordion is visible and can be expanded/collapsed', async ({ page }) => { +test('information block is always visible without accordion toggle', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); - // Find first unfinalized service const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); @@ -41,38 +39,12 @@ test('information block accordion is visible and can be expanded/collapsed', asy await editButton.click(); await page.waitForLoadState('networkidle'); - // Find the Information block toggle button - const blockToggles = page.getByTestId('service-edit-block-toggle'); - const informationToggle = blockToggles.filter({ has: page.locator('text=Information') }).first(); - - const toggleExists = await informationToggle.isVisible().catch(() => false); - if (!toggleExists) { - test.skip(); - } - - // Verify toggle button is visible - await expect(informationToggle).toBeVisible(); - - // Get the Information block content container const informationBlock = page.getByTestId('information-block'); - - // Verify block is initially visible (expanded by default) await expect(informationBlock).toBeVisible(); - // Click toggle to collapse - await informationToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is hidden - const isHidden = await informationBlock.isHidden().catch(() => true); - expect(isHidden).toBe(true); - - // Click toggle again to expand - await informationToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is visible again - await expect(informationBlock).toBeVisible(); + const blockToggles = page.getByTestId('service-edit-block-toggle'); + const toggleCount = await blockToggles.count(); + expect(toggleCount).toBe(0); }); // Test 3: Upload area is visible with drag-and-drop zone and click-to-upload diff --git a/tests/e2e/service-edit-moderation.spec.ts b/tests/e2e/service-edit-moderation.spec.ts index 8e3462f..dbe971c 100644 --- a/tests/e2e/service-edit-moderation.spec.ts +++ b/tests/e2e/service-edit-moderation.spec.ts @@ -1,204 +1,62 @@ import { test, expect } from '@playwright/test'; -// Test 1: Navigate to first editable (non-finalized) service edit page -test('navigate to first editable service edit page', async ({ page }) => { +async function navigateToEditPage(page) { await page.goto('/services'); await page.waitForLoadState('networkidle'); - // Find first unfinalized service (one with edit button) 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('navigate to first editable service edit page', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - // Click edit button - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Verify we're on the edit page await expect(page).toHaveURL(/.*services\/\d+\/edit/); - // Verify Moderation block is visible - const moderationBlock = page.getByTestId('moderation-block'); - await expect(moderationBlock).toBeVisible(); + const agendaSection = page.getByTestId('agenda-section'); + const emptyState = page.getByText('Keine Ablauf-Elemente vorhanden'); + const hasAgenda = await agendaSection.isVisible().catch(() => false); + const hasEmptyState = await emptyState.isVisible().catch(() => false); + + expect(hasAgenda || hasEmptyState).toBe(true); }); -// Test 2: Moderation block accordion is visible and can be expanded/collapsed -test('moderation block accordion is visible and can be expanded/collapsed', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); +test.skip('moderation block accordion — replaced by agenda view', async () => {}); - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { +test('agenda items with slides show slide uploader', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - await editButton.click(); - await page.waitForLoadState('networkidle'); + const agendaRows = page.getByTestId('agenda-item-row'); + const rowCount = await agendaRows.count(); - // Find the Moderation block toggle button - const blockToggles = page.getByTestId('service-edit-block-toggle'); - const moderationToggle = blockToggles.filter({ has: page.locator('text=Moderation') }).first(); - - const toggleExists = await moderationToggle.isVisible().catch(() => false); - if (!toggleExists) { + if (rowCount === 0) { test.skip(); } - // Verify toggle button is visible - await expect(moderationToggle).toBeVisible(); + const addSlidesBtn = page.getByTestId('agenda-item-add-slides').first(); + const hasSlidesBtn = await addSlidesBtn.isVisible().catch(() => false); - // Get the Moderation block content container - const moderationBlock = page.getByTestId('moderation-block'); - - // Verify block is initially visible (expanded by default) - await expect(moderationBlock).toBeVisible(); + if (!hasSlidesBtn) { + test.skip(); + } - // Click toggle to collapse - await moderationToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is hidden - const isHidden = await moderationBlock.isHidden().catch(() => true); - expect(isHidden).toBe(true); - - // Click toggle again to expand - await moderationToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is visible again - await expect(moderationBlock).toBeVisible(); + await expect(addSlidesBtn).toBeVisible(); }); -// Test 3: Upload area is visible with drag-and-drop zone (NO datepicker) -test('upload area is visible with drag-and-drop zone', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); +test.skip('existing moderation slides display as thumbnails — replaced by agenda item slides', async () => {}); - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Verify Moderation block uploader is visible - const uploader = page.getByTestId('moderation-block-uploader'); - await expect(uploader).toBeVisible(); - - // Verify dropzone is visible - const dropzone = page.getByTestId('slide-uploader-dropzone'); - await expect(dropzone).toBeVisible(); - - // Verify dropzone contains expected text - await expect(dropzone).toContainText('Dateien hier ablegen'); - await expect(dropzone).toContainText('oder klicken zum Auswählen'); - - // Verify NO expire date input (unlike Information block) - const expireInput = page.getByTestId('slide-uploader-expire-input'); - const expireInputExists = await expireInput.isVisible().catch(() => false); - expect(expireInputExists).toBe(false); -}); - -// Test 4: Existing moderation slides display as thumbnails -test('existing moderation slides display as thumbnails', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); - - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Verify slide grid is visible - const slideGrid = page.getByTestId('moderation-block-grid'); - await expect(slideGrid).toBeVisible(); - - // Check if slides exist - const slideThumbnails = page.locator('[data-testid="slide-grid-delete-button"]'); - const slideCount = await slideThumbnails.count(); - - if (slideCount === 0) { - // No slides exist - verify empty state message - const emptyState = slideGrid.locator('text=Noch keine Folien vorhanden'); - await expect(emptyState).toBeVisible(); - return; - } - - // Slides exist - verify first thumbnail is visible - const firstThumbnail = page.locator('[data-testid="slide-grid-delete-button"]').first(); - await expect(firstThumbnail).toBeVisible(); - - // Verify delete button is visible on hover - const deleteButton = firstThumbnail; - await expect(deleteButton).toBeVisible(); -}); - -// Test 5: Delete button on moderation slide thumbnail triggers confirmation -test('delete button on moderation slide thumbnail triggers confirmation', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); - - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Check if slides exist - const slideThumbnails = page.locator('[data-testid="slide-grid-delete-button"]'); - const slideCount = await slideThumbnails.count(); - - if (slideCount === 0) { - // No slides exist - skip this test - test.skip(); - } - - // Get first delete button - const firstDeleteButton = page.getByTestId('slide-grid-delete-button').first(); - await expect(firstDeleteButton).toBeVisible(); - - // Click delete button - await firstDeleteButton.click(); - await page.waitForTimeout(200); - - // Verify confirmation dialog appears - const confirmDialog = page.locator('text=Folie löschen?'); - await expect(confirmDialog).toBeVisible(); - - // Verify dialog contains expected text - await expect(page.locator('text=Möchtest du die Folie')).toBeVisible(); - await expect(page.locator('text=wirklich löschen?')).toBeVisible(); - - // Verify cancel button is visible - const cancelButton = page.locator('button:has-text("Abbrechen")').first(); - await expect(cancelButton).toBeVisible(); - - // Click cancel to close dialog without deleting - await cancelButton.click(); - await page.waitForTimeout(200); - - // Verify dialog is closed - const dialogClosed = await confirmDialog.isHidden().catch(() => true); - expect(dialogClosed).toBe(true); -}); +test.skip('delete button on moderation slide triggers confirmation — replaced by agenda item slides', async () => {}); diff --git a/tests/e2e/service-edit-sermon.spec.ts b/tests/e2e/service-edit-sermon.spec.ts index 554f0a2..98c8634 100644 --- a/tests/e2e/service-edit-sermon.spec.ts +++ b/tests/e2e/service-edit-sermon.spec.ts @@ -1,204 +1,41 @@ import { test, expect } from '@playwright/test'; -// Test 1: Navigate to first editable (non-finalized) service edit page -test('navigate to first editable service edit page', async ({ page }) => { +async function navigateToEditPage(page) { await page.goto('/services'); await page.waitForLoadState('networkidle'); - // Find first unfinalized service (one with edit button) 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('navigate to first editable service edit page', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - // Click edit button - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Verify we're on the edit page await expect(page).toHaveURL(/.*services\/\d+\/edit/); - // Verify Sermon block is visible - const sermonBlock = page.getByTestId('sermon-block'); - await expect(sermonBlock).toBeVisible(); + const agendaSection = page.getByTestId('agenda-section'); + const emptyState = page.getByText('Keine Ablauf-Elemente vorhanden'); + const hasAgenda = await agendaSection.isVisible().catch(() => false); + const hasEmptyState = await emptyState.isVisible().catch(() => false); + + expect(hasAgenda || hasEmptyState).toBe(true); }); -// Test 2: Sermon block accordion is visible and can be expanded/collapsed -test('sermon block accordion is visible and can be expanded/collapsed', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); +test.skip('sermon block accordion — replaced by agenda view', async () => {}); - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); +test.skip('sermon upload area — replaced by agenda item slide uploader', async () => {}); - if (!hasEditableService) { - test.skip(); - } +test.skip('existing sermon slides as thumbnails — replaced by agenda item slides', async () => {}); - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Find the Sermon block toggle button (3rd block) - const blockToggles = page.getByTestId('service-edit-block-toggle'); - const sermonToggle = blockToggles.filter({ has: page.locator('text=Predigt') }).first(); - - const toggleExists = await sermonToggle.isVisible().catch(() => false); - if (!toggleExists) { - test.skip(); - } - - // Verify toggle button is visible - await expect(sermonToggle).toBeVisible(); - - // Get the Sermon block content container - const sermonBlock = page.getByTestId('sermon-block'); - - // Verify block is initially visible (expanded by default) - await expect(sermonBlock).toBeVisible(); - - // Click toggle to collapse - await sermonToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is hidden - const isHidden = await sermonBlock.isHidden().catch(() => true); - expect(isHidden).toBe(true); - - // Click toggle again to expand - await sermonToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is visible again - await expect(sermonBlock).toBeVisible(); -}); - -// Test 3: Upload area is visible with drag-and-drop zone (NO datepicker) -test('upload area is visible with drag-and-drop zone', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); - - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Verify Sermon block uploader is visible - const uploader = page.getByTestId('sermon-block-uploader'); - await expect(uploader).toBeVisible(); - - // Verify dropzone is visible - const dropzone = page.getByTestId('slide-uploader-dropzone'); - await expect(dropzone).toBeVisible(); - - // Verify dropzone contains expected text - await expect(dropzone).toContainText('Dateien hier ablegen'); - await expect(dropzone).toContainText('oder klicken zum Auswählen'); - - // Verify NO expire date input (unlike Information block) - const expireInput = page.getByTestId('slide-uploader-expire-input'); - const expireInputExists = await expireInput.isVisible().catch(() => false); - expect(expireInputExists).toBe(false); -}); - -// Test 4: Existing sermon slides display as thumbnails -test('existing sermon slides display as thumbnails', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); - - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Verify slide grid is visible - const slideGrid = page.getByTestId('sermon-block-grid'); - await expect(slideGrid).toBeVisible(); - - // Check if slides exist - const slideThumbnails = page.locator('[data-testid="slide-grid-delete-button"]'); - const slideCount = await slideThumbnails.count(); - - if (slideCount === 0) { - // No slides exist - verify empty state message - const emptyState = slideGrid.locator('text=Noch keine Folien vorhanden'); - await expect(emptyState).toBeVisible(); - return; - } - - // Slides exist - verify first thumbnail is visible - const firstThumbnail = page.locator('[data-testid="slide-grid-delete-button"]').first(); - await expect(firstThumbnail).toBeVisible(); - - // Verify delete button is visible on hover - const deleteButton = firstThumbnail; - await expect(deleteButton).toBeVisible(); -}); - -// Test 5: Delete button on sermon slide thumbnail triggers confirmation -test('delete button on sermon slide thumbnail triggers confirmation', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); - - // Find first unfinalized service - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); - - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - // Check if slides exist - const slideThumbnails = page.locator('[data-testid="slide-grid-delete-button"]'); - const slideCount = await slideThumbnails.count(); - - if (slideCount === 0) { - // No slides exist - skip this test - test.skip(); - } - - // Get first delete button - const firstDeleteButton = page.getByTestId('slide-grid-delete-button').first(); - await expect(firstDeleteButton).toBeVisible(); - - // Click delete button - await firstDeleteButton.click(); - await page.waitForTimeout(200); - - // Verify confirmation dialog appears - const confirmDialog = page.locator('text=Folie löschen?'); - await expect(confirmDialog).toBeVisible(); - - // Verify dialog contains expected text - await expect(page.locator('text=Möchtest du die Folie')).toBeVisible(); - await expect(page.locator('text=wirklich löschen?')).toBeVisible(); - - // Verify cancel button is visible - const cancelButton = page.locator('button:has-text("Abbrechen")').first(); - await expect(cancelButton).toBeVisible(); - - // Click cancel to close dialog without deleting - await cancelButton.click(); - await page.waitForTimeout(200); - - // Verify dialog is closed - const dialogClosed = await confirmDialog.isHidden().catch(() => true); - expect(dialogClosed).toBe(true); -}); +test.skip('delete button on sermon slide — replaced by agenda item slides', async () => {}); diff --git a/tests/e2e/service-edit-songs.spec.ts b/tests/e2e/service-edit-songs.spec.ts index 0ca035f..fbda215 100644 --- a/tests/e2e/service-edit-songs.spec.ts +++ b/tests/e2e/service-edit-songs.spec.ts @@ -1,7 +1,6 @@ import { test, expect } from '@playwright/test'; -// Test 1: Songs block accordion can be expanded and collapsed -test('songs block accordion can be expanded and collapsed', async ({ page }) => { +async function navigateToEditPage(page) { await page.goto('/services'); await page.waitForLoadState('networkidle'); @@ -9,375 +8,184 @@ test('songs block accordion can be expanded and collapsed', async ({ page }) => const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { - test.skip(); + return false; } await editButton.click(); await page.waitForLoadState('networkidle'); + return true; +} - // Find the Songs block toggle button (4th block) - const blockToggles = page.getByTestId('service-edit-block-toggle'); - const songsToggle = blockToggles.filter({ has: page.locator('text=Songs') }).first(); +test.skip('songs block accordion — replaced by agenda view', async () => {}); - const toggleExists = await songsToggle.isVisible().catch(() => false); - if (!toggleExists) { +test('song items visible in agenda or empty state', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - await expect(songsToggle).toBeVisible(); - - // Get the Songs block content container - const songsBlock = page.getByTestId('songs-block'); - - // Verify block is initially visible (expanded by default) - await expect(songsBlock).toBeVisible(); - - // Click toggle to collapse - await songsToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is hidden - const isHidden = await songsBlock.isHidden().catch(() => true); - expect(isHidden).toBe(true); - - // Click toggle again to expand - await songsToggle.click(); - await page.waitForTimeout(300); // Wait for transition - - // Verify block is visible again - await expect(songsBlock).toBeVisible(); -}); - -// Test 2: Song list shows songs in correct order or empty state -test('song list shows songs or empty state', async ({ page }) => { - 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) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const songsBlock = page.getByTestId('songs-block'); - await expect(songsBlock).toBeVisible(); - - // Check for song cards - const songCards = page.getByTestId('songs-block-song-card'); - const songCount = await songCards.count(); + const songItems = page.getByTestId('song-agenda-item'); + const songCount = await songItems.count(); if (songCount === 0) { - // No songs - verify empty state message - await expect(songsBlock).toContainText('Fuer diesen Service sind aktuell keine Songs vorhanden.'); + const emptyState = page.getByText('Keine Ablauf-Elemente vorhanden'); + const hasEmptyState = await emptyState.isVisible().catch(() => false); + expect(hasEmptyState).toBe(true); return; } - // Songs exist - verify first card is rendered with order label - const firstSongCard = songCards.first(); - await expect(firstSongCard).toBeVisible(); - await expect(firstSongCard.locator('text=/Song \\d+/')).toBeVisible(); + const firstSongItem = songItems.first(); + await expect(firstSongItem).toBeVisible(); + + const songTitle = firstSongItem.getByTestId('song-agenda-title'); + await expect(songTitle).toBeVisible(); }); -// Test 3: Each song row shows name, CCLI ID, and status badge -test('song row shows name, CCLI ID, and status badge', async ({ page }) => { - 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) { +test('song agenda item shows title and ccli info', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const songCards = page.getByTestId('songs-block-song-card'); - const songCount = await songCards.count(); + const songItems = page.getByTestId('song-agenda-item'); + const songCount = await songItems.count(); if (songCount === 0) { test.skip(); } - const firstSongCard = songCards.first(); - - // Verify CCLI label exists - await expect(firstSongCard.locator('text=/CCLI:/')).toBeVisible(); - - // Verify translation indicator exists - await expect(firstSongCard.locator('text=/Hat Uebersetzung:/')).toBeVisible(); - - // Verify status badge exists (either "Zugeordnet" or "Nicht zugeordnet") - const statusBadge = firstSongCard.locator('text=/zugeordnet/i').first(); - await expect(statusBadge).toBeVisible(); + const firstSongItem = songItems.first(); + const songTitle = firstSongItem.getByTestId('song-agenda-title'); + await expect(songTitle).toBeVisible(); }); -// Test 4: Unmatched songs show "Erstellung anfragen" button and manual assign select -test('unmatched songs show request creation button and manual assign', async ({ page }) => { - 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) { +test('unmatched songs show request creation button in agenda', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const songCards = page.getByTestId('songs-block-song-card'); - const songCount = await songCards.count(); - - if (songCount === 0) { - test.skip(); - } - - // Look for unmatched song card (has "Nicht zugeordnet" badge) - const unmatchedCard = songCards.filter({ has: page.locator('text=Nicht zugeordnet') }).first(); - const hasUnmatched = await unmatchedCard.isVisible().catch(() => false); + const requestButton = page.getByTestId('song-request-creation').first(); + const hasUnmatched = await requestButton.isVisible().catch(() => false); if (!hasUnmatched) { test.skip(); } - // Verify "Erstellung anfragen" button - const requestButton = page.getByTestId('songs-block-request-button').first(); await expect(requestButton).toBeVisible(); - await expect(requestButton).toContainText('Erstellung anfragen'); - // Verify search input - const searchInput = page.getByTestId('songs-block-search-input').first(); + const searchInput = page.getByTestId('song-search-input').first(); await expect(searchInput).toBeVisible(); - // Verify song select dropdown - const songSelect = page.getByTestId('songs-block-song-select').first(); - await expect(songSelect).toBeVisible(); - - // Verify "Zuordnen" (assign) button - const assignButton = page.getByTestId('songs-block-assign-button').first(); + const assignButton = page.getByTestId('song-assign-button').first(); await expect(assignButton).toBeVisible(); - await expect(assignButton).toContainText('Zuordnen'); }); -// Test 5: Matched songs show arrangement dropdown with options -test('matched songs show arrangement dropdown with options', async ({ page }) => { - 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) { +test('matched songs show arrangement pill in agenda', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - await editButton.click(); - await page.waitForLoadState('networkidle'); + const arrangementPill = page.getByTestId('arrangement-pill').first(); + const hasPill = await arrangementPill.isVisible().catch(() => false); - // Check for arrangement configurator (only present for matched songs) - const arrangementConfigurator = page.getByTestId('arrangement-configurator').first(); - const hasMatched = await arrangementConfigurator.isVisible().catch(() => false); - - if (!hasMatched) { + if (!hasPill) { + test.skip(); + } + + await expect(arrangementPill).toBeVisible(); +}); + +test('arrangement edit button opens arrangement dialog', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const editArrangementBtn = page.getByTestId('song-edit-arrangement').first(); + const hasBtn = await editArrangementBtn.isVisible().catch(() => false); + + if (!hasBtn) { + test.skip(); + } + + await editArrangementBtn.click(); + await page.waitForTimeout(500); + + const arrangementDialog = page.getByTestId('arrangement-dialog'); + await expect(arrangementDialog).toBeVisible(); + + const closeBtn = page.getByTestId('arrangement-dialog-close-btn'); + await closeBtn.click(); + await page.waitForTimeout(300); +}); + +test('arrangement dialog has select, add, clone buttons', async ({ page }) => { + const navigated = await navigateToEditPage(page); + if (!navigated) { + test.skip(); + } + + const editArrangementBtn = page.getByTestId('song-edit-arrangement').first(); + const hasBtn = await editArrangementBtn.isVisible().catch(() => false); + + if (!hasBtn) { + test.skip(); + } + + await editArrangementBtn.click(); + await page.waitForTimeout(500); + + const arrangementDialog = page.getByTestId('arrangement-dialog'); + const dialogVisible = await arrangementDialog.isVisible().catch(() => false); + + if (!dialogVisible) { test.skip(); } - // Verify arrangement select dropdown exists const arrangementSelect = page.getByTestId('arrangement-select').first(); await expect(arrangementSelect).toBeVisible(); - // Verify add button - const addButton = page.getByTestId('arrangement-add-button').first(); + const addButton = page.getByTestId('arrangement-new-btn').first(); await expect(addButton).toBeVisible(); - await expect(addButton).toContainText('Hinzufügen'); - // Verify clone button - const cloneButton = page.getByTestId('arrangement-clone-button').first(); + const cloneButton = page.getByTestId('arrangement-clone-btn').first(); await expect(cloneButton).toBeVisible(); - await expect(cloneButton).toContainText('Klonen'); + + const closeBtn = page.getByTestId('arrangement-dialog-close-btn'); + await closeBtn.click(); + await page.waitForTimeout(300); }); -// Test 6: Arrangement "Hinzufügen" (Add) button opens name prompt -test('arrangement add button opens name prompt', async ({ page }) => { - await page.goto('/services'); - await page.waitForLoadState('networkidle'); +test.skip('preview button is present for matched songs — replaced by agenda view', async () => {}); - const editButton = page.getByTestId('service-list-edit-button').first(); - const hasEditableService = await editButton.isVisible().catch(() => false); +test.skip('download button is present for matched songs — replaced by agenda view', async () => {}); - if (!hasEditableService) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const addButton = page.getByTestId('arrangement-add-button').first(); - const hasMatched = await addButton.isVisible().catch(() => false); - - if (!hasMatched) { - test.skip(); - } - - // Set up dialog handler before clicking (window.prompt is synchronous) - let promptShown = false; - page.once('dialog', async (dialog) => { - promptShown = true; - expect(dialog.type()).toBe('prompt'); - expect(dialog.message()).toContain('Name des neuen Arrangements'); - await dialog.dismiss(); // Cancel without creating - }); - - await addButton.click(); - await page.waitForTimeout(500); - - expect(promptShown).toBe(true); -}); - -// Test 7: Arrangement "Klonen" (Clone) button opens name prompt -test('arrangement clone button opens name prompt', async ({ page }) => { - 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) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const cloneButton = page.getByTestId('arrangement-clone-button').first(); - const hasMatched = await cloneButton.isVisible().catch(() => false); - - if (!hasMatched) { - test.skip(); - } - - // Verify arrangement select has options (clone requires a selected arrangement) - const arrangementSelect = page.getByTestId('arrangement-select').first(); - const optionCount = await arrangementSelect.locator('option').count(); - - if (optionCount === 0) { - test.skip(); - } - - // Set up dialog handler before clicking - let promptShown = false; - page.once('dialog', async (dialog) => { - promptShown = true; - expect(dialog.type()).toBe('prompt'); - expect(dialog.message()).toContain('Name des neuen Arrangements'); - await dialog.dismiss(); // Cancel without creating - }); - - await cloneButton.click(); - await page.waitForTimeout(500); - - expect(promptShown).toBe(true); -}); - -// Test 8: Preview button opens song preview (placeholder toast for now) -test('preview button is present for matched songs', async ({ page }) => { - 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) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const previewButton = page.getByTestId('songs-block-preview-button').first(); - const hasMatched = await previewButton.isVisible().catch(() => false); - - if (!hasMatched) { - test.skip(); - } - - // Verify preview button exists with correct text - await expect(previewButton).toBeVisible(); - await expect(previewButton).toContainText('Vorschau'); -}); - -// Test 9: Download (PDF) button is present for songs with selected arrangement -test('download button is present for matched songs', async ({ page }) => { - 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) { - test.skip(); - } - - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const downloadButton = page.getByTestId('songs-block-download-button').first(); - const hasMatched = await downloadButton.isVisible().catch(() => false); - - if (!hasMatched) { - test.skip(); - } - - // Verify download button exists with correct text - await expect(downloadButton).toBeVisible(); - await expect(downloadButton).toContainText('PDF herunterladen'); -}); - -// Test 10: Translation checkbox toggles (if song has translation) test('translation checkbox toggles if song has translation', async ({ page }) => { - 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) { + const navigated = await navigateToEditPage(page); + if (!navigated) { test.skip(); } - await editButton.click(); - await page.waitForLoadState('networkidle'); - - const translationCheckbox = page.getByTestId('songs-block-translation-checkbox').first(); + const translationCheckbox = page.getByTestId('song-translation-checkbox').first(); const hasTranslation = await translationCheckbox.isVisible().catch(() => false); if (!hasTranslation) { test.skip(); } - // Get current checked state const initialState = await translationCheckbox.isChecked(); - // Toggle the checkbox await translationCheckbox.click(); await page.waitForTimeout(300); - // Verify state changed const toggledState = await translationCheckbox.isChecked(); expect(toggledState).not.toBe(initialState); - // Toggle back to restore original state await translationCheckbox.click(); await page.waitForTimeout(300); - // Verify restored to original state const restoredState = await translationCheckbox.isChecked(); expect(restoredState).toBe(initialState); });