2025-11-26 13:40:12 -05:00
|
|
|
import { test, expect } from '@playwright/test'
|
|
|
|
|
import { closeOnboardingModal } from '../../../helpers/navigation.js'
|
|
|
|
|
import { navigateToMapsV2, waitForMapLibre, waitForLoadingComplete } from '../../helpers/setup.js'
|
|
|
|
|
|
2025-11-27 15:21:53 -05:00
|
|
|
/**
|
|
|
|
|
* Helper to get the visit creation modal specifically
|
|
|
|
|
* There may be multiple modals on the page, so we need to be specific
|
|
|
|
|
*/
|
|
|
|
|
function getVisitCreationModal(page) {
|
|
|
|
|
return page.locator('[data-controller="visit-creation-v2"] .modal-box')
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 13:40:12 -05:00
|
|
|
test.describe('Visits Layer', () => {
|
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
|
|
|
await navigateToMapsV2(page)
|
|
|
|
|
await closeOnboardingModal(page)
|
|
|
|
|
await waitForMapLibre(page)
|
|
|
|
|
await waitForLoadingComplete(page)
|
|
|
|
|
await page.waitForTimeout(1500)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test.describe('Toggle', () => {
|
|
|
|
|
test('visits layer toggle exists', async ({ page }) => {
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="layers"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
|
|
|
|
|
const visitsToggle = page.locator('label:has-text("Visits")').first().locator('input.toggle')
|
|
|
|
|
await expect(visitsToggle).toBeVisible()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('can toggle visits layer', async ({ page }) => {
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="layers"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
|
|
|
|
|
const visitsToggle = page.locator('label:has-text("Visits")').first().locator('input.toggle')
|
|
|
|
|
await visitsToggle.check()
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
const isChecked = await visitsToggle.isChecked()
|
|
|
|
|
expect(isChecked).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-11-27 15:21:53 -05:00
|
|
|
|
|
|
|
|
test.describe('Visit Creation', () => {
|
|
|
|
|
test('should show Create a Visit button in Tools tab', async ({ page }) => {
|
|
|
|
|
// Open settings panel
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
|
|
|
|
|
// Click Tools tab
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
|
|
|
|
|
// Verify Create a Visit button exists
|
|
|
|
|
const createVisitButton = page.locator('button:has-text("Create a Visit")')
|
|
|
|
|
await expect(createVisitButton).toBeVisible()
|
|
|
|
|
await expect(createVisitButton).toBeEnabled()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should enable visit creation mode and show toast', async ({ page }) => {
|
|
|
|
|
// Open settings panel and click Tools tab
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
|
|
|
|
|
// Click Create a Visit button
|
|
|
|
|
await page.click('button:has-text("Create a Visit")')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Verify settings panel closed
|
|
|
|
|
const settingsPanel = page.locator('[data-maps-v2-target="settingsPanel"]')
|
|
|
|
|
const hasPanelOpenClass = await settingsPanel.evaluate((el) => el.classList.contains('open'))
|
|
|
|
|
expect(hasPanelOpenClass).toBe(false)
|
|
|
|
|
|
|
|
|
|
// Verify toast message appears
|
|
|
|
|
const toast = page.locator('.toast:has-text("Click on the map to place a visit")')
|
|
|
|
|
await expect(toast).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Verify cursor changed to crosshair
|
|
|
|
|
const cursor = await page.evaluate(() => {
|
|
|
|
|
const canvas = document.querySelector('.maplibregl-canvas')
|
|
|
|
|
return canvas?.style.cursor
|
|
|
|
|
})
|
|
|
|
|
expect(cursor).toBe('crosshair')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should open modal when map is clicked', async ({ page }) => {
|
|
|
|
|
// Enable visit creation mode
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
await page.click('button:has-text("Create a Visit")')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Click on map
|
|
|
|
|
const mapContainer = page.locator('.maplibregl-canvas')
|
|
|
|
|
const bbox = await mapContainer.boundingBox()
|
|
|
|
|
await page.mouse.click(bbox.x + bbox.width * 0.3, bbox.y + bbox.height * 0.3)
|
|
|
|
|
await page.waitForTimeout(2000)
|
|
|
|
|
|
|
|
|
|
// Verify modal title is visible (modal is open) - this is specific to visit creation modal
|
|
|
|
|
await expect(page.locator('h3:has-text("Create New Visit")')).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Verify the specific visit creation modal is visible
|
|
|
|
|
const visitModal = getVisitCreationModal(page)
|
|
|
|
|
await expect(visitModal).toBeVisible()
|
|
|
|
|
|
|
|
|
|
// Verify form has the location coordinates populated
|
|
|
|
|
const latInput = visitModal.locator('input[name="latitude"]')
|
|
|
|
|
const lngInput = visitModal.locator('input[name="longitude"]')
|
|
|
|
|
|
|
|
|
|
const latValue = await latInput.inputValue()
|
|
|
|
|
const lngValue = await lngInput.inputValue()
|
|
|
|
|
|
|
|
|
|
expect(latValue).toBeTruthy()
|
|
|
|
|
expect(lngValue).toBeTruthy()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should display correct form fields in modal', async ({ page }) => {
|
|
|
|
|
// Enable mode and click map
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
await page.click('button:has-text("Create a Visit")')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
const mapContainer = page.locator('.maplibregl-canvas')
|
|
|
|
|
const bbox = await mapContainer.boundingBox()
|
|
|
|
|
await page.mouse.click(bbox.x + bbox.width * 0.3, bbox.y + bbox.height * 0.3)
|
|
|
|
|
await page.waitForTimeout(1500)
|
|
|
|
|
|
|
|
|
|
// Wait for modal to be visible
|
|
|
|
|
const visitModal = getVisitCreationModal(page)
|
|
|
|
|
await expect(visitModal).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Verify all form fields exist within the visit creation modal
|
|
|
|
|
await expect(visitModal.locator('input[name="name"]')).toBeVisible()
|
|
|
|
|
await expect(visitModal.locator('input[name="started_at"]')).toBeVisible()
|
|
|
|
|
await expect(visitModal.locator('input[name="ended_at"]')).toBeVisible()
|
|
|
|
|
await expect(visitModal.locator('input[data-visit-creation-v2-target="locationDisplay"]')).toBeVisible()
|
|
|
|
|
await expect(visitModal.locator('button:has-text("Adjust")')).toBeVisible()
|
|
|
|
|
await expect(visitModal.locator('button:has-text("Create Visit")')).toBeVisible()
|
|
|
|
|
await expect(visitModal.locator('button:has-text("Cancel")')).toBeVisible()
|
|
|
|
|
|
|
|
|
|
// Verify start and end time have default values
|
|
|
|
|
const startValue = await visitModal.locator('input[name="started_at"]').inputValue()
|
|
|
|
|
const endValue = await visitModal.locator('input[name="ended_at"]').inputValue()
|
|
|
|
|
expect(startValue).toBeTruthy()
|
|
|
|
|
expect(endValue).toBeTruthy()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should close modal when cancel is clicked', async ({ page }) => {
|
|
|
|
|
// Enable mode and click map
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Click Create a Visit button
|
|
|
|
|
const createButton = page.locator('button:has-text("Create a Visit")')
|
|
|
|
|
await expect(createButton).toBeVisible()
|
|
|
|
|
await createButton.click()
|
|
|
|
|
await page.waitForTimeout(1000)
|
|
|
|
|
|
|
|
|
|
// Wait for settings panel to close and cursor to change
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Click on map - try a different location
|
|
|
|
|
const mapContainer = page.locator('.maplibregl-canvas')
|
|
|
|
|
const bbox = await mapContainer.boundingBox()
|
|
|
|
|
await page.mouse.click(bbox.x + bbox.width * 0.5, bbox.y + bbox.height * 0.5)
|
|
|
|
|
await page.waitForTimeout(2500)
|
|
|
|
|
|
|
|
|
|
// Verify modal exists
|
|
|
|
|
const visitModal = getVisitCreationModal(page)
|
|
|
|
|
await expect(visitModal).toBeVisible({ timeout: 10000 })
|
|
|
|
|
|
|
|
|
|
// Find the cancel button - it's a ghost button
|
|
|
|
|
const cancelButton = visitModal.locator('button.btn-ghost:has-text("Cancel")')
|
|
|
|
|
await expect(cancelButton).toBeVisible()
|
|
|
|
|
await cancelButton.click()
|
|
|
|
|
await page.waitForTimeout(1500)
|
|
|
|
|
|
|
|
|
|
// Verify modal is closed by checking if modal-open class is removed
|
|
|
|
|
const modal = page.locator('[data-controller="visit-creation-v2"] .modal')
|
|
|
|
|
const hasModalOpenClass = await modal.evaluate((el) => el.classList.contains('modal-open'))
|
|
|
|
|
expect(hasModalOpenClass).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should create visit successfully', async ({ page }) => {
|
|
|
|
|
// Enable visits layer first
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="layers"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
const visitsToggle = page.locator('label:has-text("Visits")').first().locator('input.toggle')
|
|
|
|
|
await visitsToggle.check()
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Enable visit creation mode
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
await page.click('button:has-text("Create a Visit")')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Click on map
|
|
|
|
|
const mapContainer = page.locator('.maplibregl-canvas')
|
|
|
|
|
const bbox = await mapContainer.boundingBox()
|
|
|
|
|
await page.mouse.click(bbox.x + bbox.width * 0.3, bbox.y + bbox.height * 0.3)
|
|
|
|
|
await page.waitForTimeout(2000)
|
|
|
|
|
|
|
|
|
|
// Wait for modal to be visible
|
|
|
|
|
const visitModal = getVisitCreationModal(page)
|
|
|
|
|
await expect(visitModal).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Fill form with unique visit name
|
|
|
|
|
const visitName = `E2E V2 Test Visit ${Date.now()}`
|
|
|
|
|
await visitModal.locator('input[name="name"]').fill(visitName)
|
|
|
|
|
|
|
|
|
|
// Submit form
|
|
|
|
|
await visitModal.locator('button:has-text("Create Visit")').click()
|
|
|
|
|
|
|
|
|
|
// Wait for success toast - this confirms the visit was created
|
|
|
|
|
const successToast = page.locator('.toast:has-text("created successfully")')
|
|
|
|
|
await expect(successToast).toBeVisible({ timeout: 10000 })
|
|
|
|
|
|
|
|
|
|
// Verify modal is closed by checking if modal-open class is removed
|
|
|
|
|
await page.waitForTimeout(1500)
|
|
|
|
|
const modal = page.locator('[data-controller="visit-creation-v2"] .modal')
|
|
|
|
|
const hasModalOpenClass = await modal.evaluate((el) => el.classList.contains('modal-open'))
|
|
|
|
|
expect(hasModalOpenClass).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should make created visit searchable in side panel', async ({ page }) => {
|
|
|
|
|
// Enable visits layer
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="layers"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
const visitsToggle = page.locator('label:has-text("Visits")').first().locator('input.toggle')
|
|
|
|
|
await visitsToggle.check()
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Create a visit with unique name
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
await page.click('button:has-text("Create a Visit")')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
const mapContainer = page.locator('.maplibregl-canvas')
|
|
|
|
|
const bbox = await mapContainer.boundingBox()
|
|
|
|
|
await page.mouse.click(bbox.x + bbox.width * 0.3, bbox.y + bbox.height * 0.3)
|
|
|
|
|
await page.waitForTimeout(2000)
|
|
|
|
|
|
|
|
|
|
// Wait for modal to be visible
|
|
|
|
|
const visitModal = getVisitCreationModal(page)
|
|
|
|
|
await expect(visitModal).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
const visitName = `Searchable Visit ${Date.now()}`
|
|
|
|
|
await visitModal.locator('input[name="name"]').fill(visitName)
|
|
|
|
|
await visitModal.locator('button:has-text("Create Visit")').click()
|
|
|
|
|
|
|
|
|
|
// Wait for success toast
|
|
|
|
|
const successToast = page.locator('.toast:has-text("created successfully")')
|
|
|
|
|
await expect(successToast).toBeVisible({ timeout: 10000 })
|
|
|
|
|
|
|
|
|
|
// Wait for modal to close
|
|
|
|
|
await page.waitForTimeout(1500)
|
|
|
|
|
|
|
|
|
|
// Open settings and go to layers tab to access visit search
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="layers"]')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Search field should now be visible (bug fix ensures it shows when toggle is checked)
|
|
|
|
|
const searchField = page.locator('input#visits-search')
|
|
|
|
|
await expect(searchField).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Use the visit search field
|
|
|
|
|
await searchField.fill(visitName.substring(0, 10))
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Verify the search field is working - just check that it accepted the input
|
|
|
|
|
const searchValue = await searchField.inputValue()
|
|
|
|
|
expect(searchValue).toBe(visitName.substring(0, 10))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('should validate required fields', async ({ page }) => {
|
|
|
|
|
// Enable visit creation mode
|
|
|
|
|
await page.click('button[title="Open map settings"]')
|
|
|
|
|
await page.waitForTimeout(400)
|
|
|
|
|
await page.click('button[data-tab="tools"]')
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
await page.click('button:has-text("Create a Visit")')
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Click on map
|
|
|
|
|
const mapContainer = page.locator('.maplibregl-canvas')
|
|
|
|
|
const bbox = await mapContainer.boundingBox()
|
|
|
|
|
await page.mouse.click(bbox.x + bbox.width * 0.3, bbox.y + bbox.height * 0.3)
|
|
|
|
|
await page.waitForTimeout(1500)
|
|
|
|
|
|
|
|
|
|
// Wait for modal to be visible
|
|
|
|
|
const visitModal = getVisitCreationModal(page)
|
|
|
|
|
await expect(visitModal).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Clear the name field
|
|
|
|
|
await visitModal.locator('input[name="name"]').clear()
|
|
|
|
|
|
|
|
|
|
// Try to submit form without name
|
|
|
|
|
await visitModal.locator('button:has-text("Create Visit")').click()
|
|
|
|
|
await page.waitForTimeout(500)
|
|
|
|
|
|
|
|
|
|
// Verify modal is still open (form validation prevented submission)
|
|
|
|
|
const modalVisible = await visitModal.isVisible()
|
|
|
|
|
expect(modalVisible).toBe(true)
|
|
|
|
|
|
|
|
|
|
// Verify name field has validation error (HTML5 validation)
|
|
|
|
|
const isNameValid = await visitModal.locator('input[name="name"]').evaluate((el) => el.validity.valid)
|
|
|
|
|
expect(isNameValid).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-11-26 13:40:12 -05:00
|
|
|
})
|