add itemsearch tests to cypress search testing

This commit is contained in:
mahula 2025-10-05 11:59:41 +02:00
parent 81349e299b
commit eeee4b9d01
4 changed files with 123 additions and 3 deletions

View File

@ -0,0 +1,50 @@
/// <reference types="cypress" />
describe('Utopia Map Item Search', () => {
beforeEach(() => {
cy.clearCookies()
cy.clearLocalStorage()
cy.window().then((win) => {
win.sessionStorage.clear()
})
cy.visit('/')
cy.waitForMapReady()
})
it('should find items by exact name match', () => {
cy.searchFor('Tech Meetup Munich')
cy.get('[data-cy="search-suggestions"]').should('contain', 'Tech Meetup Munich')
})
it('should find items by partial name match (case insensitive)', () => {
cy.searchFor('café collaboration')
cy.get('[data-cy="search-suggestions"]').should('contain', 'Café Collaboration London')
})
it('should find items by text content', () => {
cy.searchFor('sustainability')
cy.get('[data-cy="search-suggestions"]').should('contain', 'Alex Entrepreneur')
})
it('should navigate to item profile when clicking search result', () => {
cy.intercept('GET', '**/items*').as('getItems')
cy.visit('/')
cy.waitForMapReady()
cy.searchFor('makerspace')
cy.get('[data-cy="search-suggestions"]').within(() => {
cy.get('[data-cy="search-item-result"]').contains('Makerspace Tokyo').click()
})
cy.url().should('match', /\/(item\/|[a-f0-9-]+)/)
cy.get('body').should('contain', 'Makerspace Tokyo')
// Verify search uses local filtering (items already loaded)
cy.get('@getItems.all').then((calls) => {
expect(calls.length).to.be.greaterThan(0)
})
})
})

View File

@ -87,6 +87,7 @@ export default tseslint.config(
'cypress/downloads/**',
'cypress/screenshots/**',
'cypress/videos/**',
'results/**',
'dist/**',
'build/**',
'*.min.js'

View File

@ -22,6 +22,43 @@ declare global {
* @example cy.waitForMapReady()
*/
waitForMapReady(): Chainable<Element>
/**
* Click on a map marker
* @example cy.clickMarker() // clicks first marker
*/
clickMarker(): Chainable<Element>
/**
* Wait for a popup to appear on the map
* @example cy.waitForPopup()
*/
waitForPopup(): Chainable<Element>
/**
* Close the currently open popup
* @example cy.closePopup()
*/
closePopup(): Chainable<Element>
/**
* Toggle a layer's visibility in the layer control
* @param layerName - Name of the layer to toggle
* @example cy.toggleLayer('places')
*/
toggleLayer(layerName: string): Chainable<Element>
/**
* Open the layer control panel
* @example cy.openLayerControl()
*/
openLayerControl(): Chainable<Element>
/**
* Close the layer control panel
* @example cy.closeLayerControl()
*/
closeLayerControl(): Chainable<Element>
}
}
}
@ -42,4 +79,30 @@ Cypress.Commands.add('waitForMapReady', () => {
cy.get('.leaflet-marker-icon', { timeout: 15000 }).should('have.length.at.least', 1)
})
Cypress.Commands.add('clickMarker', () => {
// For now, always use force click since markers might be clustered or outside viewport
cy.get('.leaflet-marker-icon').first().click({ force: true })
})
Cypress.Commands.add('waitForPopup', () => {
cy.get('[data-cy="item-popup"]', { timeout: 10000 }).should('be.visible')
})
Cypress.Commands.add('closePopup', () => {
cy.get('.leaflet-popup-close-button').click()
})
Cypress.Commands.add('toggleLayer', (layerName: string) => {
cy.get(`[data-cy="layer-checkbox-${layerName}"]`).click()
})
Cypress.Commands.add('openLayerControl', () => {
cy.get('[data-cy="layer-control-button"]').click()
cy.get('[data-cy="layer-control-panel"]', { timeout: 5000 }).should('be.visible')
})
Cypress.Commands.add('closeLayerControl', () => {
cy.get('[data-cy="layer-control-close"]').click()
})
export {}

View File

@ -113,6 +113,7 @@ export const SearchControl = () => {
autoComplete='off'
value={value}
className='tw:input tw:input-bordered tw:h-12 tw:grow tw:shadow-xl tw:rounded-box tw:pr-12 tw:w-full'
data-cy='search-input'
ref={searchInput}
onChange={(e) => setValue(e.target.value)}
onFocus={() => {
@ -124,6 +125,7 @@ export const SearchControl = () => {
{value.length > 0 && (
<button
className='tw:btn tw:btn-sm tw:btn-circle tw:absolute tw:right-2 tw:top-2'
data-cy='search-clear-button'
onClick={() => setValue('')}
>
@ -140,13 +142,14 @@ export const SearchControl = () => {
value.length === 0 ? (
''
) : (
<div className='tw:card tw:card-body tw:bg-base-100 tw:p-4 tw:mt-2 tw:shadow-xl tw:overflow-y-auto tw:max-h-[calc(100dvh-152px)] tw:absolute tw:z-3000 tw:w-83'>
<div className='tw:card tw:card-body tw:bg-base-100 tw:p-4 tw:mt-2 tw:shadow-xl tw:overflow-y-auto tw:max-h-[calc(100dvh-152px)] tw:absolute tw:z-3000 tw:w-83' data-cy='search-suggestions'>
{tagsResults.length > 0 && (
<div className='tw:flex tw:flex-wrap'>
{tagsResults.slice(0, 3).map((tag) => (
<div
key={tag.name}
className='tw:rounded-2xl tw:text-white tw:p-1 tw:px-4 tw:shadow-md tw:card tw:mr-2 tw:mb-2 tw:cursor-pointer'
data-cy='search-tag-result'
style={{ backgroundColor: tag.color }}
onClick={() => {
addFilterTag(tag)
@ -165,6 +168,7 @@ export const SearchControl = () => {
<div
key={item.id}
className='tw:cursor-pointer tw:hover:font-bold tw:flex tw:flex-row'
data-cy='search-item-result'
onClick={() => {
const marker = Object.entries(leafletRefs).find((r) => r[1].item === item)?.[1]
.marker
@ -178,7 +182,7 @@ export const SearchControl = () => {
}}
>
{item.layer?.markerIcon.image ? (
<div className='tw:w-7 tw:h-full tw:flex tw:justify-center tw:items-center'>
<div className='tw:w-7 tw:h-full tw:flex tw:justify-center tw:items-center' data-cy='search-item-icon'>
<SVG
src={appState.assetsApi.url + item.layer.markerIcon.image}
className='tw:text-current tw:mr-2 tw:mt-0'
@ -191,7 +195,7 @@ export const SearchControl = () => {
/>
</div>
) : (
<div className='tw:w-7' />
<div className='tw:w-7' data-cy='search-item-icon-placeholder' />
)}
<div>
<div className='tw:text-sm tw:overflow-hidden tw:text-ellipsis tw:whitespace-nowrap tw:max-w-[17rem]'>
@ -207,6 +211,7 @@ export const SearchControl = () => {
{Array.from(geoResults).map((geo) => (
<div
className='tw:flex tw:flex-row tw:hover:font-bold tw:cursor-pointer'
data-cy='search-geo-result'
key={Math.random()}
onClick={() => {
searchInput.current?.blur()
@ -262,6 +267,7 @@ export const SearchControl = () => {
{isGeoCoordinate(value) && (
<div
className='tw:flex tw:flex-row tw:hover:font-bold tw:cursor-pointer'
data-cy='search-coordinate-result'
onClick={() => {
marker(
new LatLng(extractCoordinates(value)![0], extractCoordinates(value)![1]),