From b0bd2bf93cbe9ce7228d465d3f551058a8e07270 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Sun, 9 Nov 2025 16:03:05 +0100 Subject: [PATCH] Update tests --- CHANGELOG.md | 4 +- app/javascript/maps/visits.js | 26 +++--- e2e/map/map-side-panel.spec.js | 111 +++++++++++++++++++++----- e2e/map/map-visits.spec.js | 25 ++++-- spec/services/families/invite_spec.rb | 3 +- 5 files changed, 124 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cdc2558..96bebfd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,14 +15,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Taiwan flag is now shown on its own instead of in combination with China flag. - On the registration page and other user forms, if something goes wrong, error messages are now shown to the user. - Leaving family, deleting family and cancelling invitations now prompt confirmation dialog to prevent accidental actions. -- Each pending family invitation now also contain a link to share with the invitee. +- Each pending family invitation now also contains a link to share with the invitee. ## Changed - Removed useless system tests and cover map functionality with Playwright e2e tests instead. - S3 storage now can be used in self-hosted instances as well. Set STORAGE_BACKEND environment variable to `s3` and provide `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_BUCKET` and `AWS_ENDPOINT_URL` environment variables to configure it. - Number of family members on self-hosted instances is no longer limited. #1918 -- Export to GPX now adds adds speed and course to each point if they are available. +- Export to GPX now adds speed and course to each point if they are available. - `docker-compose.yml` file updated to provide sensible defaults for self-hosted production environment. - `.env.example` file added with default environment variables. - Single Dockerfile introduced so Dawarich could be run in self-hosted mode in production environment. diff --git a/app/javascript/maps/visits.js b/app/javascript/maps/visits.js index 026ba384..a05e0963 100644 --- a/app/javascript/maps/visits.js +++ b/app/javascript/maps/visits.js @@ -398,18 +398,15 @@ export class VisitsManager { * Adds a cancel button to the drawer to clear the selection */ addSelectionCancelButton() { - console.log('addSelectionCancelButton: Called'); const container = document.getElementById('visits-list'); if (!container) { console.error('addSelectionCancelButton: visits-list container not found'); return; } - console.log('addSelectionCancelButton: Container found'); // Remove any existing button container first to avoid duplicates const existingButtonContainer = document.getElementById('selection-button-container'); if (existingButtonContainer) { - console.log('addSelectionCancelButton: Removing existing button container'); existingButtonContainer.remove(); } @@ -438,9 +435,6 @@ export class VisitsManager { badge.className = 'badge badge-sm ml-1'; badge.textContent = this.selectedPoints.length; deleteButton.appendChild(badge); - console.log(`addSelectionCancelButton: Added badge with ${this.selectedPoints.length} points`); - } else { - console.warn('addSelectionCancelButton: No selected points, selectedPoints =', this.selectedPoints); } buttonContainer.appendChild(cancelButton); @@ -448,15 +442,6 @@ export class VisitsManager { // Insert at the beginning of the container container.insertBefore(buttonContainer, container.firstChild); - console.log('addSelectionCancelButton: Buttons inserted into DOM'); - - // Verify buttons are in DOM - setTimeout(() => { - const verifyDelete = document.getElementById('delete-selection-button'); - const verifyCancel = document.getElementById('cancel-selection-button'); - console.log('addSelectionCancelButton: Verification - Delete button exists:', !!verifyDelete); - console.log('addSelectionCancelButton: Verification - Cancel button exists:', !!verifyCancel); - }, 100); } /** @@ -596,12 +581,21 @@ export class VisitsManager { const controlsLayer = { Points: this.mapsController.markersLayer || L.layerGroup(), Routes: this.mapsController.polylinesLayer || L.layerGroup(), + Tracks: this.mapsController.tracksLayer || L.layerGroup(), Heatmap: this.mapsController.heatmapLayer || L.layerGroup(), "Fog of War": this.mapsController.fogOverlay, "Scratch map": this.mapsController.scratchLayerManager?.getLayer() || L.layerGroup(), Areas: this.mapsController.areasLayer || L.layerGroup(), - Photos: this.mapsController.photoMarkers || L.layerGroup() + Photos: this.mapsController.photoMarkers || L.layerGroup(), + "Suggested Visits": this.getVisitCirclesLayer(), + "Confirmed Visits": this.getConfirmedVisitCirclesLayer() }; + + // Include Family Members layer if available + if (window.familyMembersController?.familyMarkersLayer) { + controlsLayer['Family Members'] = window.familyMembersController.familyMarkersLayer; + } + this.mapsController.layerControl = L.control.layers( this.mapsController.baseMaps(), controlsLayer diff --git a/e2e/map/map-side-panel.spec.js b/e2e/map/map-side-panel.spec.js index f8d4f79f..e09284ed 100644 --- a/e2e/map/map-side-panel.spec.js +++ b/e2e/map/map-side-panel.spec.js @@ -161,6 +161,14 @@ test.describe('Side Panel', () => { test('should display visit details in panel', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + // Check if we have any visits const visitCount = await page.locator('.visit-item').count(); @@ -185,7 +193,7 @@ test.describe('Side Panel', () => { await expect(timeInfo).toBeVisible(); // Check if this is a suggested visit (has confirm/decline buttons) - const hasSuggestedButtons = await firstVisit.locator('.confirm-visit').count() > 0; + const hasSuggestedButtons = (await firstVisit.locator('.confirm-visit').count()) > 0; if (hasSuggestedButtons) { // For suggested visits, verify action buttons are present @@ -202,6 +210,14 @@ test.describe('Side Panel', () => { test('should confirm individual suggested visit from panel', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + // Find a suggested visit (one with confirm/decline buttons) const suggestedVisit = page.locator('.visit-item').filter({ has: page.locator('.confirm-visit') }).first(); @@ -245,6 +261,14 @@ test.describe('Side Panel', () => { test('should decline individual suggested visit from panel', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + // Find a suggested visit const suggestedVisit = page.locator('.visit-item').filter({ has: page.locator('.decline-visit') }).first(); @@ -280,6 +304,14 @@ test.describe('Side Panel', () => { test('should show checkboxes on hover for mass selection', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + // Check if we have any visits const visitCount = await page.locator('.visit-item').count(); @@ -313,6 +345,14 @@ test.describe('Side Panel', () => { test('should select multiple visits and show bulk action buttons', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + // Verify we have at least 2 visits const visitCount = await page.locator('.visit-item').count(); if (visitCount < 2) { @@ -365,6 +405,14 @@ test.describe('Side Panel', () => { test('should cancel mass selection', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + const visitCount = await page.locator('.visit-item').count(); if (visitCount < 2) { console.log('Test skipped: Need at least 2 visits'); @@ -405,6 +453,14 @@ test.describe('Side Panel', () => { test('should mass confirm multiple visits', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + // Find suggested visits (those with confirm buttons) const suggestedVisits = page.locator('.visit-item').filter({ has: page.locator('.confirm-visit') }); const suggestedCount = await suggestedVisits.count(); @@ -452,6 +508,14 @@ test.describe('Side Panel', () => { test('should mass decline multiple visits', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + const suggestedVisits = page.locator('.visit-item').filter({ has: page.locator('.decline-visit') }); const suggestedCount = await suggestedVisits.count(); @@ -497,6 +561,14 @@ test.describe('Side Panel', () => { test('should mass merge multiple visits', async ({ page }) => { await selectAreaWithVisits(page); + // Open the visits collapsible section + const visitsSection = page.locator('#visits-section-collapse'); + await expect(visitsSection).toBeVisible(); + + const visitsSummary = visitsSection.locator('summary'); + await visitsSummary.click(); + await page.waitForTimeout(500); + const visitCount = await page.locator('.visit-item').count(); if (visitCount < 2) { console.log('Test skipped: Need at least 2 visits'); @@ -535,35 +607,38 @@ test.describe('Side Panel', () => { expect(finalVisitCount).toBeLessThan(visitCount); }); - test('should shift controls when panel opens and shift back when closed', async ({ page }) => { - // Get initial position of a control element (layer control) + test('should open and close panel without shifting controls', async ({ page }) => { + // Get the layer control element const layerControl = page.locator('.leaflet-control-layers'); await expect(layerControl).toBeVisible(); - // Check if controls have the shifted class initially (should not) - const initiallyShifted = await layerControl.evaluate(el => - el.classList.contains('controls-shifted') - ); - expect(initiallyShifted).toBe(false); + // Get initial position of the control + const initialBox = await layerControl.boundingBox(); // Open the drawer await clickDrawerButton(page); await page.waitForTimeout(500); - // Verify controls now have the shifted class - const shiftedAfterOpen = await layerControl.evaluate(el => - el.classList.contains('controls-shifted') - ); - expect(shiftedAfterOpen).toBe(true); + // Verify drawer is open + const drawerOpen = await isDrawerOpen(page); + expect(drawerOpen).toBe(true); + + // Get position after opening - should be the same (no shifting) + const afterOpenBox = await layerControl.boundingBox(); + expect(afterOpenBox.x).toBe(initialBox.x); + expect(afterOpenBox.y).toBe(initialBox.y); // Close the drawer await clickDrawerButton(page); await page.waitForTimeout(500); - // Verify controls no longer have the shifted class - const shiftedAfterClose = await layerControl.evaluate(el => - el.classList.contains('controls-shifted') - ); - expect(shiftedAfterClose).toBe(false); + // Verify drawer is closed + const drawerClosed = await isDrawerOpen(page); + expect(drawerClosed).toBe(false); + + // Get final position - should still be the same + const afterCloseBox = await layerControl.boundingBox(); + expect(afterCloseBox.x).toBe(initialBox.x); + expect(afterCloseBox.y).toBe(initialBox.y); }); }); diff --git a/e2e/map/map-visits.spec.js b/e2e/map/map-visits.spec.js index 67e85e19..4633b274 100644 --- a/e2e/map/map-visits.spec.js +++ b/e2e/map/map-visits.spec.js @@ -132,10 +132,15 @@ test.describe('Visit Interactions', () => { await saveButton.click(); await page.waitForTimeout(1000); - // Verify success message or popup closes - const popupStillVisible = await page.locator('.leaflet-popup').isVisible().catch(() => false); - // Either popup closes or stays open with updated content - expect(popupStillVisible === false || popupStillVisible === true).toBe(true); + // Verify popup closes after successful save + const popupVisible = await page.locator('.leaflet-popup').isVisible().catch(() => false); + expect(popupVisible).toBe(false); + + // Verify success flash message appears + const flashMessage = page.locator('#flash-messages [role="alert"]'); + await expect(flashMessage).toBeVisible({ timeout: 2000 }); + const messageText = await flashMessage.textContent(); + expect(messageText).toContain('Visit updated successfully'); } }); @@ -173,9 +178,15 @@ test.describe('Visit Interactions', () => { await saveButton.click(); await page.waitForTimeout(1000); - // Verify flash message or popup closes - const flashOrClose = await page.locator('#flash-messages [role="alert"]').isVisible({ timeout: 2000 }).catch(() => false); - expect(flashOrClose === true || flashOrClose === false).toBe(true); + // Verify popup closes after successful save + const popupVisible = await page.locator('.leaflet-popup').isVisible().catch(() => false); + expect(popupVisible).toBe(false); + + // Verify success flash message appears + const flashMessage = page.locator('#flash-messages [role="alert"]'); + await expect(flashMessage).toBeVisible({ timeout: 2000 }); + const messageText = await flashMessage.textContent(); + expect(messageText).toContain('Visit updated successfully'); } }); diff --git a/spec/services/families/invite_spec.rb b/spec/services/families/invite_spec.rb index 19ec4b5f..7a2df4f8 100644 --- a/spec/services/families/invite_spec.rb +++ b/spec/services/families/invite_spec.rb @@ -27,8 +27,7 @@ RSpec.describe Families::Invite do end it 'sends invitation email' do - expect(FamilyMailer).to receive(:invitation).and_call_original - expect_any_instance_of(ActionMailer::MessageDelivery).to receive(:deliver_later) + expect(Family::Invitations::SendingJob).to receive(:perform_later).and_call_original service.call end