mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Update tests
This commit is contained in:
parent
3061f3e86f
commit
b0bd2bf93c
5 changed files with 124 additions and 45 deletions
|
|
@ -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.
|
- 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.
|
- 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.
|
- 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
|
## Changed
|
||||||
|
|
||||||
- Removed useless system tests and cover map functionality with Playwright e2e tests instead.
|
- 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.
|
- 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
|
- 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.
|
- `docker-compose.yml` file updated to provide sensible defaults for self-hosted production environment.
|
||||||
- `.env.example` file added with default environment variables.
|
- `.env.example` file added with default environment variables.
|
||||||
- Single Dockerfile introduced so Dawarich could be run in self-hosted mode in production environment.
|
- Single Dockerfile introduced so Dawarich could be run in self-hosted mode in production environment.
|
||||||
|
|
|
||||||
|
|
@ -398,18 +398,15 @@ export class VisitsManager {
|
||||||
* Adds a cancel button to the drawer to clear the selection
|
* Adds a cancel button to the drawer to clear the selection
|
||||||
*/
|
*/
|
||||||
addSelectionCancelButton() {
|
addSelectionCancelButton() {
|
||||||
console.log('addSelectionCancelButton: Called');
|
|
||||||
const container = document.getElementById('visits-list');
|
const container = document.getElementById('visits-list');
|
||||||
if (!container) {
|
if (!container) {
|
||||||
console.error('addSelectionCancelButton: visits-list container not found');
|
console.error('addSelectionCancelButton: visits-list container not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('addSelectionCancelButton: Container found');
|
|
||||||
|
|
||||||
// Remove any existing button container first to avoid duplicates
|
// Remove any existing button container first to avoid duplicates
|
||||||
const existingButtonContainer = document.getElementById('selection-button-container');
|
const existingButtonContainer = document.getElementById('selection-button-container');
|
||||||
if (existingButtonContainer) {
|
if (existingButtonContainer) {
|
||||||
console.log('addSelectionCancelButton: Removing existing button container');
|
|
||||||
existingButtonContainer.remove();
|
existingButtonContainer.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,9 +435,6 @@ export class VisitsManager {
|
||||||
badge.className = 'badge badge-sm ml-1';
|
badge.className = 'badge badge-sm ml-1';
|
||||||
badge.textContent = this.selectedPoints.length;
|
badge.textContent = this.selectedPoints.length;
|
||||||
deleteButton.appendChild(badge);
|
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);
|
buttonContainer.appendChild(cancelButton);
|
||||||
|
|
@ -448,15 +442,6 @@ export class VisitsManager {
|
||||||
|
|
||||||
// Insert at the beginning of the container
|
// Insert at the beginning of the container
|
||||||
container.insertBefore(buttonContainer, container.firstChild);
|
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 = {
|
const controlsLayer = {
|
||||||
Points: this.mapsController.markersLayer || L.layerGroup(),
|
Points: this.mapsController.markersLayer || L.layerGroup(),
|
||||||
Routes: this.mapsController.polylinesLayer || L.layerGroup(),
|
Routes: this.mapsController.polylinesLayer || L.layerGroup(),
|
||||||
|
Tracks: this.mapsController.tracksLayer || L.layerGroup(),
|
||||||
Heatmap: this.mapsController.heatmapLayer || L.layerGroup(),
|
Heatmap: this.mapsController.heatmapLayer || L.layerGroup(),
|
||||||
"Fog of War": this.mapsController.fogOverlay,
|
"Fog of War": this.mapsController.fogOverlay,
|
||||||
"Scratch map": this.mapsController.scratchLayerManager?.getLayer() || L.layerGroup(),
|
"Scratch map": this.mapsController.scratchLayerManager?.getLayer() || L.layerGroup(),
|
||||||
Areas: this.mapsController.areasLayer || 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.layerControl = L.control.layers(
|
||||||
this.mapsController.baseMaps(),
|
this.mapsController.baseMaps(),
|
||||||
controlsLayer
|
controlsLayer
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,14 @@ test.describe('Side Panel', () => {
|
||||||
test('should display visit details in panel', async ({ page }) => {
|
test('should display visit details in panel', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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
|
// Check if we have any visits
|
||||||
const visitCount = await page.locator('.visit-item').count();
|
const visitCount = await page.locator('.visit-item').count();
|
||||||
|
|
||||||
|
|
@ -185,7 +193,7 @@ test.describe('Side Panel', () => {
|
||||||
await expect(timeInfo).toBeVisible();
|
await expect(timeInfo).toBeVisible();
|
||||||
|
|
||||||
// Check if this is a suggested visit (has confirm/decline buttons)
|
// 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) {
|
if (hasSuggestedButtons) {
|
||||||
// For suggested visits, verify action buttons are present
|
// 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 }) => {
|
test('should confirm individual suggested visit from panel', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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)
|
// Find a suggested visit (one with confirm/decline buttons)
|
||||||
const suggestedVisit = page.locator('.visit-item').filter({ has: page.locator('.confirm-visit') }).first();
|
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 }) => {
|
test('should decline individual suggested visit from panel', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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
|
// Find a suggested visit
|
||||||
const suggestedVisit = page.locator('.visit-item').filter({ has: page.locator('.decline-visit') }).first();
|
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 }) => {
|
test('should show checkboxes on hover for mass selection', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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
|
// Check if we have any visits
|
||||||
const visitCount = await page.locator('.visit-item').count();
|
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 }) => {
|
test('should select multiple visits and show bulk action buttons', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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
|
// Verify we have at least 2 visits
|
||||||
const visitCount = await page.locator('.visit-item').count();
|
const visitCount = await page.locator('.visit-item').count();
|
||||||
if (visitCount < 2) {
|
if (visitCount < 2) {
|
||||||
|
|
@ -365,6 +405,14 @@ test.describe('Side Panel', () => {
|
||||||
test('should cancel mass selection', async ({ page }) => {
|
test('should cancel mass selection', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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();
|
const visitCount = await page.locator('.visit-item').count();
|
||||||
if (visitCount < 2) {
|
if (visitCount < 2) {
|
||||||
console.log('Test skipped: Need at least 2 visits');
|
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 }) => {
|
test('should mass confirm multiple visits', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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)
|
// Find suggested visits (those with confirm buttons)
|
||||||
const suggestedVisits = page.locator('.visit-item').filter({ has: page.locator('.confirm-visit') });
|
const suggestedVisits = page.locator('.visit-item').filter({ has: page.locator('.confirm-visit') });
|
||||||
const suggestedCount = await suggestedVisits.count();
|
const suggestedCount = await suggestedVisits.count();
|
||||||
|
|
@ -452,6 +508,14 @@ test.describe('Side Panel', () => {
|
||||||
test('should mass decline multiple visits', async ({ page }) => {
|
test('should mass decline multiple visits', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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 suggestedVisits = page.locator('.visit-item').filter({ has: page.locator('.decline-visit') });
|
||||||
const suggestedCount = await suggestedVisits.count();
|
const suggestedCount = await suggestedVisits.count();
|
||||||
|
|
||||||
|
|
@ -497,6 +561,14 @@ test.describe('Side Panel', () => {
|
||||||
test('should mass merge multiple visits', async ({ page }) => {
|
test('should mass merge multiple visits', async ({ page }) => {
|
||||||
await selectAreaWithVisits(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();
|
const visitCount = await page.locator('.visit-item').count();
|
||||||
if (visitCount < 2) {
|
if (visitCount < 2) {
|
||||||
console.log('Test skipped: Need at least 2 visits');
|
console.log('Test skipped: Need at least 2 visits');
|
||||||
|
|
@ -535,35 +607,38 @@ test.describe('Side Panel', () => {
|
||||||
expect(finalVisitCount).toBeLessThan(visitCount);
|
expect(finalVisitCount).toBeLessThan(visitCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should shift controls when panel opens and shift back when closed', async ({ page }) => {
|
test('should open and close panel without shifting controls', async ({ page }) => {
|
||||||
// Get initial position of a control element (layer control)
|
// Get the layer control element
|
||||||
const layerControl = page.locator('.leaflet-control-layers');
|
const layerControl = page.locator('.leaflet-control-layers');
|
||||||
await expect(layerControl).toBeVisible();
|
await expect(layerControl).toBeVisible();
|
||||||
|
|
||||||
// Check if controls have the shifted class initially (should not)
|
// Get initial position of the control
|
||||||
const initiallyShifted = await layerControl.evaluate(el =>
|
const initialBox = await layerControl.boundingBox();
|
||||||
el.classList.contains('controls-shifted')
|
|
||||||
);
|
|
||||||
expect(initiallyShifted).toBe(false);
|
|
||||||
|
|
||||||
// Open the drawer
|
// Open the drawer
|
||||||
await clickDrawerButton(page);
|
await clickDrawerButton(page);
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
// Verify controls now have the shifted class
|
// Verify drawer is open
|
||||||
const shiftedAfterOpen = await layerControl.evaluate(el =>
|
const drawerOpen = await isDrawerOpen(page);
|
||||||
el.classList.contains('controls-shifted')
|
expect(drawerOpen).toBe(true);
|
||||||
);
|
|
||||||
expect(shiftedAfterOpen).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
|
// Close the drawer
|
||||||
await clickDrawerButton(page);
|
await clickDrawerButton(page);
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
// Verify controls no longer have the shifted class
|
// Verify drawer is closed
|
||||||
const shiftedAfterClose = await layerControl.evaluate(el =>
|
const drawerClosed = await isDrawerOpen(page);
|
||||||
el.classList.contains('controls-shifted')
|
expect(drawerClosed).toBe(false);
|
||||||
);
|
|
||||||
expect(shiftedAfterClose).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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -132,10 +132,15 @@ test.describe('Visit Interactions', () => {
|
||||||
await saveButton.click();
|
await saveButton.click();
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
// Verify success message or popup closes
|
// Verify popup closes after successful save
|
||||||
const popupStillVisible = await page.locator('.leaflet-popup').isVisible().catch(() => false);
|
const popupVisible = await page.locator('.leaflet-popup').isVisible().catch(() => false);
|
||||||
// Either popup closes or stays open with updated content
|
expect(popupVisible).toBe(false);
|
||||||
expect(popupStillVisible === false || popupStillVisible === true).toBe(true);
|
|
||||||
|
// 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 saveButton.click();
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
// Verify flash message or popup closes
|
// Verify popup closes after successful save
|
||||||
const flashOrClose = await page.locator('#flash-messages [role="alert"]').isVisible({ timeout: 2000 }).catch(() => false);
|
const popupVisible = await page.locator('.leaflet-popup').isVisible().catch(() => false);
|
||||||
expect(flashOrClose === true || flashOrClose === false).toBe(true);
|
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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ RSpec.describe Families::Invite do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends invitation email' do
|
it 'sends invitation email' do
|
||||||
expect(FamilyMailer).to receive(:invitation).and_call_original
|
expect(Family::Invitations::SendingJob).to receive(:perform_later).and_call_original
|
||||||
expect_any_instance_of(ActionMailer::MessageDelivery).to receive(:deliver_later)
|
|
||||||
service.call
|
service.call
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue