diff --git a/app/javascript/maps/visits.js b/app/javascript/maps/visits.js index 32c4af06..43e11b5e 100644 --- a/app/javascript/maps/visits.js +++ b/app/javascript/maps/visits.js @@ -227,7 +227,8 @@ export class VisitsManager { this.toggleDrawer(); } - // Add cancel selection button to the drawer + // Add cancel selection button to the drawer AFTER displayVisits + // This needs to be after because displayVisits sets innerHTML which would wipe out the buttons this.addSelectionCancelButton(); } catch (error) { @@ -390,42 +391,53 @@ export class VisitsManager { */ addSelectionCancelButton() { const container = document.getElementById('visits-list'); - if (!container) return; - - // Add buttons at the top of the drawer if they don't exist - if (!document.getElementById('cancel-selection-button')) { - // Create a button container - const buttonContainer = document.createElement('div'); - buttonContainer.className = 'flex gap-2 mb-4'; - - // Cancel button - const cancelButton = document.createElement('button'); - cancelButton.id = 'cancel-selection-button'; - cancelButton.className = 'btn btn-sm btn-warning flex-1'; - cancelButton.textContent = 'Cancel Selection'; - cancelButton.onclick = () => this.clearSelection(); - - // Delete all selected points button - const deleteButton = document.createElement('button'); - deleteButton.id = 'delete-selection-button'; - deleteButton.className = 'btn btn-sm btn-error flex-1'; - deleteButton.innerHTML = 'Delete Points'; - deleteButton.onclick = () => this.deleteSelectedPoints(); - - // Add count badge if we have selected points - if (this.selectedPoints && this.selectedPoints.length > 0) { - const badge = document.createElement('span'); - badge.className = 'badge badge-sm ml-1'; - badge.textContent = this.selectedPoints.length; - deleteButton.appendChild(badge); - } - - buttonContainer.appendChild(cancelButton); - buttonContainer.appendChild(deleteButton); - - // Insert at the beginning of the container - container.insertBefore(buttonContainer, container.firstChild); + if (!container) { + console.warn('addSelectionCancelButton: visits-list container not found'); + return; } + + // Remove any existing button container first to avoid duplicates + const existingButtonContainer = container.querySelector('.flex.gap-2.mb-4'); + if (existingButtonContainer) { + existingButtonContainer.remove(); + } + + // Create a button container + const buttonContainer = document.createElement('div'); + buttonContainer.className = 'flex gap-2 mb-4'; + buttonContainer.id = 'selection-button-container'; + + // Cancel button + const cancelButton = document.createElement('button'); + cancelButton.id = 'cancel-selection-button'; + cancelButton.className = 'btn btn-sm btn-warning flex-1'; + cancelButton.textContent = 'Cancel Selection'; + cancelButton.onclick = () => this.clearSelection(); + + // Delete all selected points button + const deleteButton = document.createElement('button'); + deleteButton.id = 'delete-selection-button'; + deleteButton.className = 'btn btn-sm btn-error flex-1'; + deleteButton.innerHTML = 'Delete Points'; + deleteButton.onclick = () => this.deleteSelectedPoints(); + + // Add count badge if we have selected points + if (this.selectedPoints && this.selectedPoints.length > 0) { + const badge = document.createElement('span'); + badge.className = 'badge badge-sm ml-1'; + badge.textContent = this.selectedPoints.length; + deleteButton.appendChild(badge); + console.log(`addSelectionCancelButton: Added button with badge showing ${this.selectedPoints.length} points`); + } else { + console.warn('addSelectionCancelButton: No selected points found'); + } + + buttonContainer.appendChild(cancelButton); + buttonContainer.appendChild(deleteButton); + + // Insert at the beginning of the container + container.insertBefore(buttonContainer, container.firstChild); + console.log('addSelectionCancelButton: Buttons added successfully'); } /** diff --git a/e2e/bulk-delete-points.spec.js b/e2e/bulk-delete-points.spec.js index 789d9a9c..32d5551c 100644 --- a/e2e/bulk-delete-points.spec.js +++ b/e2e/bulk-delete-points.spec.js @@ -1,5 +1,38 @@ const { test, expect } = require('@playwright/test'); +// Helper function to draw selection rectangle and wait for delete button +async function drawSelectionRectangle(page) { + // Click area selection tool + const selectionButton = page.locator('#selection-tool-button'); + await selectionButton.click(); + await page.waitForTimeout(500); + + // Draw a rectangle on the map to select points + const mapContainer = page.locator('#map [data-maps-target="container"]'); + const bbox = await mapContainer.boundingBox(); + + // Draw rectangle covering most of the map to ensure we select points + const startX = bbox.x + bbox.width * 0.2; + const startY = bbox.y + bbox.height * 0.2; + const endX = bbox.x + bbox.width * 0.8; + const endY = bbox.y + bbox.height * 0.8; + + await page.mouse.move(startX, startY); + await page.mouse.down(); + await page.mouse.move(endX, endY, { steps: 10 }); // Add steps for smoother drag + await page.mouse.up(); + + // Wait longer for API calls and drawer animations + await page.waitForTimeout(2000); + + // Wait for drawer to open (it should open automatically after selection) + await page.waitForSelector('#visits-drawer.open', { timeout: 15000 }); + + // Wait for delete button to appear in the drawer (indicates selection is complete) + await page.waitForSelector('#delete-selection-button', { timeout: 15000 }); + await page.waitForTimeout(500); // Brief wait for UI to stabilize +} + test.describe('Bulk Delete Points', () => { test.beforeEach(async ({ page }) => { // Navigate to map page @@ -72,54 +105,18 @@ test.describe('Bulk Delete Points', () => { }); test('should select points in drawn area and show delete button', async ({ page }) => { - // Click area selection tool - const selectionButton = page.locator('#selection-tool-button'); - await selectionButton.click(); - await page.waitForTimeout(500); - - // Draw a rectangle on the map to select points - const mapContainer = page.locator('#map [data-maps-target="container"]'); - const bbox = await mapContainer.boundingBox(); - - // Draw rectangle from top-left to bottom-right - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; - - await page.mouse.move(startX, startY); - await page.mouse.down(); - await page.mouse.move(endX, endY); - await page.mouse.up(); - await page.waitForTimeout(1000); + await drawSelectionRectangle(page); // Check that delete button appears const deleteButton = page.locator('#delete-selection-button'); - await expect(deleteButton).toBeVisible(); + await expect(deleteButton).toBeVisible({ timeout: 10000 }); // Check button has text "Delete Points" await expect(deleteButton).toContainText('Delete Points'); }); test('should show point count badge on delete button', async ({ page }) => { - // Click area selection tool - const selectionButton = page.locator('#selection-tool-button'); - await selectionButton.click(); - await page.waitForTimeout(500); - - // Draw rectangle - const mapContainer = page.locator('#map [data-maps-target="container"]'); - const bbox = await mapContainer.boundingBox(); - - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; - - await page.mouse.move(startX, startY); - await page.mouse.down(); - await page.mouse.move(endX, endY); - await page.mouse.up(); + await drawSelectionRectangle(page); await page.waitForTimeout(1000); // Check for badge with count @@ -132,24 +129,7 @@ test.describe('Bulk Delete Points', () => { }); test('should show cancel button alongside delete button', async ({ page }) => { - // Click area selection tool - const selectionButton = page.locator('#selection-tool-button'); - await selectionButton.click(); - await page.waitForTimeout(500); - - // Draw rectangle - const mapContainer = page.locator('#map [data-maps-target="container"]'); - const bbox = await mapContainer.boundingBox(); - - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; - - await page.mouse.move(startX, startY); - await page.mouse.down(); - await page.mouse.move(endX, endY); - await page.mouse.up(); + await drawSelectionRectangle(page); await page.waitForTimeout(1000); // Check both buttons exist @@ -162,23 +142,7 @@ test.describe('Bulk Delete Points', () => { }); test('should cancel selection when cancel button is clicked', async ({ page }) => { - // Click area selection tool and draw rectangle - const selectionButton = page.locator('#selection-tool-button'); - await selectionButton.click(); - await page.waitForTimeout(500); - - const mapContainer = page.locator('#map [data-maps-target="container"]'); - const bbox = await mapContainer.boundingBox(); - - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; - - await page.mouse.move(startX, startY); - await page.mouse.down(); - await page.mouse.move(endX, endY); - await page.mouse.up(); + await drawSelectionRectangle(page); await page.waitForTimeout(1000); // Click cancel button @@ -207,23 +171,7 @@ test.describe('Bulk Delete Points', () => { await dialog.dismiss(); // Dismiss to prevent actual deletion }); - // Click area selection tool and draw rectangle - const selectionButton = page.locator('#selection-tool-button'); - await selectionButton.click(); - await page.waitForTimeout(500); - - const mapContainer = page.locator('#map [data-maps-target="container"]'); - const bbox = await mapContainer.boundingBox(); - - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; - - await page.mouse.move(startX, startY); - await page.mouse.down(); - await page.mouse.move(endX, endY); - await page.mouse.up(); + await drawSelectionRectangle(page); await page.waitForTimeout(1000); // Click delete button @@ -249,23 +197,7 @@ test.describe('Bulk Delete Points', () => { return controller?.markers?.length || 0; }); - // Click area selection tool and draw rectangle - const selectionButton = page.locator('#selection-tool-button'); - await selectionButton.click(); - await page.waitForTimeout(500); - - const mapContainer = page.locator('#map [data-maps-target="container"]'); - const bbox = await mapContainer.boundingBox(); - - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; - - await page.mouse.move(startX, startY); - await page.mouse.down(); - await page.mouse.move(endX, endY); - await page.mouse.up(); + await drawSelectionRectangle(page); await page.waitForTimeout(1000); // Click delete button @@ -273,8 +205,8 @@ test.describe('Bulk Delete Points', () => { await deleteButton.click(); await page.waitForTimeout(2000); // Wait for deletion to complete - // Check for success flash message - const flashMessage = page.locator('#flash-messages [role="alert"]'); + // Check for success flash message with specific text + const flashMessage = page.locator('#flash-messages [role="alert"]:has-text("Successfully deleted")'); await expect(flashMessage).toBeVisible({ timeout: 5000 }); const messageText = await flashMessage.textContent(); @@ -306,7 +238,7 @@ test.describe('Bulk Delete Points', () => { await dialog.accept(); }); - // Perform deletion + // Perform deletion using same selection logic as helper const selectionButton = page.locator('#selection-tool-button'); await selectionButton.click(); await page.waitForTimeout(500); @@ -314,16 +246,21 @@ test.describe('Bulk Delete Points', () => { const mapContainer = page.locator('#map [data-maps-target="container"]'); const bbox = await mapContainer.boundingBox(); - const startX = bbox.x + bbox.width * 0.4; - const startY = bbox.y + bbox.height * 0.4; - const endX = bbox.x + bbox.width * 0.6; - const endY = bbox.y + bbox.height * 0.6; + // Use larger selection area to ensure we select points + const startX = bbox.x + bbox.width * 0.2; + const startY = bbox.y + bbox.height * 0.2; + const endX = bbox.x + bbox.width * 0.8; + const endY = bbox.y + bbox.height * 0.8; await page.mouse.move(startX, startY); await page.mouse.down(); - await page.mouse.move(endX, endY); + await page.mouse.move(endX, endY, { steps: 10 }); await page.mouse.up(); - await page.waitForTimeout(1000); + await page.waitForTimeout(2000); + + // Wait for drawer and button to appear + await page.waitForSelector('#visits-drawer.open', { timeout: 15000 }); + await page.waitForSelector('#delete-selection-button', { timeout: 15000 }); const deleteButton = page.locator('#delete-selection-button'); await deleteButton.click(); @@ -355,7 +292,7 @@ test.describe('Bulk Delete Points', () => { await dialog.accept(); }); - // Perform deletion + // Perform deletion using same selection logic as helper const selectionButton = page.locator('#selection-tool-button'); await selectionButton.click(); await page.waitForTimeout(500); @@ -363,16 +300,21 @@ test.describe('Bulk Delete Points', () => { const mapContainer = page.locator('#map [data-maps-target="container"]'); const bbox = await mapContainer.boundingBox(); - const startX = bbox.x + bbox.width * 0.4; - const startY = bbox.y + bbox.height * 0.4; - const endX = bbox.x + bbox.width * 0.6; - const endY = bbox.y + bbox.height * 0.6; + // Use larger selection area to ensure we select points + const startX = bbox.x + bbox.width * 0.2; + const startY = bbox.y + bbox.height * 0.2; + const endX = bbox.x + bbox.width * 0.8; + const endY = bbox.y + bbox.height * 0.8; await page.mouse.move(startX, startY); await page.mouse.down(); - await page.mouse.move(endX, endY); + await page.mouse.move(endX, endY, { steps: 10 }); await page.mouse.up(); - await page.waitForTimeout(1000); + await page.waitForTimeout(2000); + + // Wait for drawer and button to appear + await page.waitForSelector('#visits-drawer.open', { timeout: 15000 }); + await page.waitForSelector('#delete-selection-button', { timeout: 15000 }); const deleteButton = page.locator('#delete-selection-button'); await deleteButton.click(); @@ -410,7 +352,7 @@ test.describe('Bulk Delete Points', () => { await dialog.accept(); }); - // Perform deletion + // Perform deletion using same selection logic as helper const selectionButton = page.locator('#selection-tool-button'); await selectionButton.click(); await page.waitForTimeout(500); @@ -418,16 +360,21 @@ test.describe('Bulk Delete Points', () => { const mapContainer = page.locator('#map [data-maps-target="container"]'); const bbox = await mapContainer.boundingBox(); - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; + // Use larger selection area to ensure we select points + const startX = bbox.x + bbox.width * 0.2; + const startY = bbox.y + bbox.height * 0.2; + const endX = bbox.x + bbox.width * 0.8; + const endY = bbox.y + bbox.height * 0.8; await page.mouse.move(startX, startY); await page.mouse.down(); - await page.mouse.move(endX, endY); + await page.mouse.move(endX, endY, { steps: 10 }); await page.mouse.up(); - await page.waitForTimeout(1000); + await page.waitForTimeout(2000); + + // Wait for drawer and button to appear + await page.waitForSelector('#visits-drawer.open', { timeout: 15000 }); + await page.waitForSelector('#delete-selection-button', { timeout: 15000 }); const deleteButton = page.locator('#delete-selection-button'); await deleteButton.click(); @@ -448,7 +395,7 @@ test.describe('Bulk Delete Points', () => { await dialog.accept(); }); - // Perform deletion + // Perform deletion using same selection logic as helper const selectionButton = page.locator('#selection-tool-button'); await selectionButton.click(); await page.waitForTimeout(500); @@ -456,16 +403,21 @@ test.describe('Bulk Delete Points', () => { const mapContainer = page.locator('#map [data-maps-target="container"]'); const bbox = await mapContainer.boundingBox(); - const startX = bbox.x + bbox.width * 0.3; - const startY = bbox.y + bbox.height * 0.3; - const endX = bbox.x + bbox.width * 0.7; - const endY = bbox.y + bbox.height * 0.7; + // Use larger selection area to ensure we select points + const startX = bbox.x + bbox.width * 0.2; + const startY = bbox.y + bbox.height * 0.2; + const endX = bbox.x + bbox.width * 0.8; + const endY = bbox.y + bbox.height * 0.8; await page.mouse.move(startX, startY); await page.mouse.down(); - await page.mouse.move(endX, endY); + await page.mouse.move(endX, endY, { steps: 10 }); await page.mouse.up(); - await page.waitForTimeout(1000); + await page.waitForTimeout(2000); + + // Wait for drawer and button to appear + await page.waitForSelector('#visits-drawer.open', { timeout: 15000 }); + await page.waitForSelector('#delete-selection-button', { timeout: 15000 }); const deleteButton = page.locator('#delete-selection-button'); await deleteButton.click(); diff --git a/e2e/temp/.auth/user.json b/e2e/temp/.auth/user.json index 7f178f83..da478652 100644 --- a/e2e/temp/.auth/user.json +++ b/e2e/temp/.auth/user.json @@ -2,7 +2,7 @@ "cookies": [ { "name": "_dawarich_session", - "value": "EpVyt%2F73ZRFf3PGkUELvrrnllSZ7fNY8oLGYvmO0STevmBL9bT9XZb9JK4NE6KSDMYqDLPFSRrZTNAlmyOuYi7kett2QE3TjNcAVVtE8%2BRhUweTPTcFs9wwAbf%2FlKYqQkMLF4NYz%2FA0Mr39M2fLxx0qAiqAo0Cg4y1jHQlWS1Slrp%2FkXkHt3obK5z6biG8gqXk9ldBqa6Uh3ymuBJOe%2BQE0rvhnsGRfD%2FIFbsgUzCuU3BEHw%2BUaO%2FYR%2BrlASj4sNiBr6%2FBRLlI0pecI4G8avHHSasFigpw2JEslgP12ifFtoLd5yw7uqO0K7eUF9oGAWV3KWvj7xScfDi4mYagFDpu8q5msipd6Wo6e5D7i8GjnxhoGDLuBRHqIxS76EhxTHl%2FE%2FkV146ZFH--YqOqiWcq7Oafo4bF--F2LpPqfUyhiln%2B9dabKFxQ%3D%3D", + "value": "uSq%2BCBWS9YujuNhicVcBHgR62tcYRgXVobd6flpFfMCPEGldb7vMuvaYDitf%2Fr%2FRMXC2Vb4VKE0dHz00pXp1S%2F6gBJuXHZ05is2zoZPGQ5gRaGVRbEG%2F%2FfKVSKgOb3B87WmmrB4I%2B1jq10hT0MI%2FKzeAhIR%2BI1hVmGYeozx%2BNttmWjIgtYk%2FN9JnsN7jzmYvON65mrRgJQyPftaIEpOYpMCdbPl1uosRoQpO6WroGxQ4J89lFvoSbyTspqJje8A0i5JNJThSfRkyPcIPXNtIFGHUUBX376%2BOLZds7sLor6%2BMu%2FQs%2FnjQl2xWFxejo6lSp%2BZrplztjPwquQtjm1BmnWN1PPvFsrphEw2scIk%2BQUyk2F3ZQcVwBUExB0dm5qA9M3uSbvR%2BV8OU--8oAuBe0ygLxh0x4N--STVFL7XMmRGm17xka6AU3A%3D%3D", "domain": "localhost", "path": "/", "expires": -1,