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