mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
* Implement OmniAuth GitHub authentication * Fix omniauth GitHub scope to include user email access * Remove margin-bottom * Implement Google OAuth2 authentication * Implement OIDC authentication for Dawarich using omniauth_openid_connect gem. * Add patreon account linking and patron checking service * Update docker-compose.yml to use boolean values instead of strings * Add support for KML files * Add tests * Update changelog * Remove patreon OAuth integration * Move omniauthable to a concern * Update an icon in integrations * Update changelog * Update app version * Fix family location sharing toggle * Move family location sharing to its own controller * Update changelog * Implement basic tagging functionality for places, allowing users to categorize and label places with custom tags. * Add places management API and tags feature * Add some changes related to places management feature * Fix some tests * Fix sometests * Add places layer * Update places layer to use Leaflet.Control.Layers.Tree for hierarchical layer control * Rework tag form * Add hashtag * Add privacy zones to tags * Add notes to places and manage place tags * Update changelog * Update e2e tests * Extract tag serializer to its own file * Fix some tests * Fix tags request specs * Fix some tests * Fix rest of the tests * Revert some changes * Add missing specs * Revert changes in place export/import code * Fix some specs * Fix PlaceFinder to only consider global places when finding existing places * Fix few more specs * Fix visits creator spec * Fix last tests * Update place creating modal * Add home location based on "Home" tagged place * Save enabled tag layers * Some fixes * Fix bug where enabling place tag layers would trigger saving enabled layers, overwriting with incomplete data * Update migration to use disable_ddl_transaction! and add up/down methods * Fix tag layers restoration and filtering logic * Update OIDC auto-registration and email/password registration settings * Fix potential xss
334 lines
12 KiB
JavaScript
334 lines
12 KiB
JavaScript
import { test, expect } from '@playwright/test';
|
|
import { navigateToMap } from '../helpers/navigation.js';
|
|
import { waitForMap } from '../helpers/map.js';
|
|
|
|
test.describe('Places Creation', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateToMap(page);
|
|
await waitForMap(page);
|
|
});
|
|
|
|
test('should enable place creation mode when "Create a place" button is clicked', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Verify button exists
|
|
await expect(createPlaceBtn).toBeVisible();
|
|
|
|
// Click to enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Verify creation mode is enabled
|
|
const isCreationMode = await page.evaluate(() => {
|
|
const controller = window.Stimulus?.controllers.find(c => c.identifier === 'maps');
|
|
return controller?.placesManager?.creationMode === true;
|
|
});
|
|
|
|
expect(isCreationMode).toBe(true);
|
|
});
|
|
|
|
test('should change button icon to X when in place creation mode', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Click to enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Verify button tooltip changed
|
|
const tooltip = await createPlaceBtn.getAttribute('data-tip');
|
|
expect(tooltip).toContain('click to cancel');
|
|
|
|
// Verify button has active state
|
|
const hasActiveClass = await createPlaceBtn.evaluate((btn) => {
|
|
return btn.classList.contains('active') ||
|
|
btn.style.backgroundColor !== '' ||
|
|
btn.hasAttribute('data-active');
|
|
});
|
|
|
|
expect(hasActiveClass).toBe(true);
|
|
});
|
|
|
|
test('should exit place creation mode when X button is clicked', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Click again to disable
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Verify creation mode is disabled
|
|
const isCreationMode = await page.evaluate(() => {
|
|
const controller = window.Stimulus?.controllers.find(c => c.identifier === 'maps');
|
|
return controller?.placesManager?.creationMode === true;
|
|
});
|
|
|
|
expect(isCreationMode).toBe(false);
|
|
});
|
|
|
|
test('should open place creation popup when map is clicked in creation mode', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Get map container and click on it
|
|
const mapContainer = page.locator('#map');
|
|
await mapContainer.click({ position: { x: 300, y: 300 } });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Verify modal is open
|
|
const modalOpen = await page.locator('[data-place-creation-target="modal"]').evaluate((modal) => {
|
|
return modal.classList.contains('modal-open');
|
|
});
|
|
|
|
expect(modalOpen).toBe(true);
|
|
|
|
// Verify form fields exist (latitude/longitude are hidden inputs, so we check they exist, not visibility)
|
|
await expect(page.locator('[data-place-creation-target="nameInput"]')).toBeVisible();
|
|
await expect(page.locator('[data-place-creation-target="latitudeInput"]')).toBeAttached();
|
|
await expect(page.locator('[data-place-creation-target="longitudeInput"]')).toBeAttached();
|
|
});
|
|
|
|
test('should allow user to provide name, notes and select tags in creation popup', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Click on map
|
|
const mapContainer = page.locator('#map');
|
|
await mapContainer.click({ position: { x: 300, y: 300 } });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Fill in the form
|
|
const nameInput = page.locator('[data-place-creation-target="nameInput"]');
|
|
await nameInput.fill('Test Place');
|
|
|
|
const noteInput = page.locator('textarea[name="note"]');
|
|
if (await noteInput.isVisible()) {
|
|
await noteInput.fill('This is a test note');
|
|
}
|
|
|
|
// Check if there are any tag checkboxes to select
|
|
const tagCheckboxes = page.locator('input[name="tag_ids[]"]');
|
|
const tagCount = await tagCheckboxes.count();
|
|
if (tagCount > 0) {
|
|
await tagCheckboxes.first().check();
|
|
}
|
|
|
|
// Verify fields are filled
|
|
await expect(nameInput).toHaveValue('Test Place');
|
|
});
|
|
|
|
test('should save place when Save button is clicked @destructive', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Click on map
|
|
const mapContainer = page.locator('#map');
|
|
await mapContainer.click({ position: { x: 300, y: 300 } });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Fill in the form with a unique name
|
|
const placeName = `E2E Test Place ${Date.now()}`;
|
|
const nameInput = page.locator('[data-place-creation-target="nameInput"]');
|
|
await nameInput.fill(placeName);
|
|
|
|
// Submit form
|
|
const submitBtn = page.locator('[data-place-creation-target="form"] button[type="submit"]');
|
|
|
|
// Set up a promise to wait for the place:created event
|
|
const placeCreatedPromise = page.evaluate(() => {
|
|
return new Promise((resolve) => {
|
|
document.addEventListener('place:created', (e) => {
|
|
resolve(e.detail);
|
|
}, { once: true });
|
|
});
|
|
});
|
|
|
|
await submitBtn.click();
|
|
|
|
// Wait for the place to be created
|
|
await placeCreatedPromise;
|
|
|
|
// Verify modal is closed
|
|
await page.waitForTimeout(500);
|
|
const modalOpen = await page.locator('[data-place-creation-target="modal"]').evaluate((modal) => {
|
|
return modal.classList.contains('modal-open');
|
|
});
|
|
|
|
expect(modalOpen).toBe(false);
|
|
|
|
// Verify success message is shown
|
|
const hasSuccessMessage = await page.evaluate(() => {
|
|
const flashMessages = document.querySelectorAll('.alert, .flash, [role="alert"]');
|
|
return Array.from(flashMessages).some(msg =>
|
|
msg.textContent.includes('success') ||
|
|
msg.classList.contains('alert-success')
|
|
);
|
|
});
|
|
|
|
expect(hasSuccessMessage).toBe(true);
|
|
});
|
|
|
|
test('should put clickable marker on map after saving place @destructive', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Click on map
|
|
const mapContainer = page.locator('#map');
|
|
await mapContainer.click({ position: { x: 300, y: 300 } });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Fill and submit form
|
|
const placeName = `E2E Test Place ${Date.now()}`;
|
|
await page.locator('[data-place-creation-target="nameInput"]').fill(placeName);
|
|
|
|
const placeCreatedPromise = page.evaluate(() => {
|
|
return new Promise((resolve) => {
|
|
document.addEventListener('place:created', (e) => {
|
|
resolve(e.detail);
|
|
}, { once: true });
|
|
});
|
|
});
|
|
|
|
await page.locator('[data-place-creation-target="form"] button[type="submit"]').click();
|
|
await placeCreatedPromise;
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Verify marker was added to the map
|
|
const hasMarker = await page.evaluate(() => {
|
|
const controller = window.Stimulus?.controllers.find(c => c.identifier === 'maps');
|
|
const placesLayer = controller?.placesManager?.placesLayer;
|
|
|
|
if (!placesLayer || !placesLayer._layers) {
|
|
return false;
|
|
}
|
|
|
|
return Object.keys(placesLayer._layers).length > 0;
|
|
});
|
|
|
|
expect(hasMarker).toBe(true);
|
|
});
|
|
|
|
test('should close popup and remove marker when Cancel is clicked', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Click on map
|
|
const mapContainer = page.locator('#map');
|
|
await mapContainer.click({ position: { x: 300, y: 300 } });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Check if creation marker exists
|
|
const hasCreationMarkerBefore = await page.evaluate(() => {
|
|
const controller = window.Stimulus?.controllers.find(c => c.identifier === 'maps');
|
|
return controller?.placesManager?.creationMarker !== null;
|
|
});
|
|
|
|
expect(hasCreationMarkerBefore).toBe(true);
|
|
|
|
// Click cancel
|
|
const cancelBtn = page.locator('[data-place-creation-target="modal"] button').filter({ hasText: /cancel|close/i }).first();
|
|
await cancelBtn.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Verify modal is closed
|
|
const modalOpen = await page.locator('[data-place-creation-target="modal"]').evaluate((modal) => {
|
|
return modal.classList.contains('modal-open');
|
|
});
|
|
|
|
expect(modalOpen).toBe(false);
|
|
|
|
// Verify creation marker is removed
|
|
const hasCreationMarkerAfter = await page.evaluate(() => {
|
|
const controller = window.Stimulus?.controllers.find(c => c.identifier === 'maps');
|
|
return controller?.placesManager?.creationMarker !== null;
|
|
});
|
|
|
|
expect(hasCreationMarkerAfter).toBe(false);
|
|
});
|
|
|
|
test('should close previous popup and open new one when clicking different location', async ({ page }) => {
|
|
const createPlaceBtn = page.locator('#create-place-btn');
|
|
|
|
// Enable creation mode
|
|
await createPlaceBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Click first location
|
|
const mapContainer = page.locator('#map');
|
|
await mapContainer.click({ position: { x: 300, y: 300 } });
|
|
await page.waitForTimeout(500);
|
|
|
|
// Get first coordinates
|
|
const firstCoords = await page.evaluate(() => {
|
|
const latInput = document.querySelector('[data-place-creation-target="latitudeInput"]');
|
|
const lngInput = document.querySelector('[data-place-creation-target="longitudeInput"]');
|
|
return {
|
|
lat: latInput?.value,
|
|
lng: lngInput?.value
|
|
};
|
|
});
|
|
|
|
// Verify first coordinates exist
|
|
expect(firstCoords.lat).toBeTruthy();
|
|
expect(firstCoords.lng).toBeTruthy();
|
|
|
|
// Use programmatic click to simulate clicking on a different map location
|
|
// This bypasses UI interference with modal
|
|
const secondCoords = await page.evaluate(() => {
|
|
const controller = window.Stimulus?.controllers.find(c => c.identifier === 'maps');
|
|
if (controller && controller.placesManager && controller.placesManager.creationMode) {
|
|
// Simulate clicking at a different location
|
|
const map = controller.map;
|
|
const center = map.getCenter();
|
|
const newLatlng = { lat: center.lat + 0.01, lng: center.lng + 0.01 };
|
|
|
|
// Trigger place creation at new location
|
|
controller.placesManager.handleMapClick({ latlng: newLatlng });
|
|
|
|
// Wait for UI update
|
|
return new Promise(resolve => {
|
|
setTimeout(() => {
|
|
const latInput = document.querySelector('[data-place-creation-target="latitudeInput"]');
|
|
const lngInput = document.querySelector('[data-place-creation-target="longitudeInput"]');
|
|
resolve({
|
|
lat: latInput?.value,
|
|
lng: lngInput?.value
|
|
});
|
|
}, 100);
|
|
});
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// Verify second coordinates exist and are different from first
|
|
expect(secondCoords).toBeTruthy();
|
|
expect(secondCoords.lat).toBeTruthy();
|
|
expect(secondCoords.lng).toBeTruthy();
|
|
expect(firstCoords.lat).not.toBe(secondCoords.lat);
|
|
expect(firstCoords.lng).not.toBe(secondCoords.lng);
|
|
|
|
// Verify modal is still open
|
|
const modalOpen = await page.locator('[data-place-creation-target="modal"]').evaluate((modal) => {
|
|
return modal.classList.contains('modal-open');
|
|
});
|
|
|
|
expect(modalOpen).toBe(true);
|
|
});
|
|
});
|