Update tests

This commit is contained in:
Eugene Burmakin
2025-11-09 16:03:05 +01:00
parent 3061f3e86f
commit b0bd2bf93c
5 changed files with 124 additions and 45 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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);
});
});

View File

@@ -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');
}
});

View File

@@ -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