feat: add flights map e2e tests (74 tests for map rendering, list, interactions, clustering, geolocation, responsive design, api, and state)

This commit is contained in:
2026-04-04 12:20:03 +03:00
parent 0ca49b9bf3
commit 2caa5c81fe
@@ -0,0 +1,662 @@
/// <reference types="cypress" />
import { CITIES } from '../../support/fixtures';
describe('Flights Map Feature', () => {
// Mock data for destinations
const mockDestinations = {
data: {
routes: [
{
arrivalCity: {
name: 'Москва',
code: 'MOW',
location: {
lat: 55.7558,
lon: 37.6173,
},
},
flightCount: 12,
directFlightCount: 8,
},
{
arrivalCity: {
name: 'Санкт-Петербург',
code: 'LED',
location: {
lat: 59.8011,
lon: 30.2642,
},
},
flightCount: 5,
directFlightCount: 3,
},
{
arrivalCity: {
name: 'Анапа',
code: 'AAQ',
location: {
lat: 44.8972,
lon: 37.3426,
},
},
flightCount: 7,
directFlightCount: 5,
},
{
arrivalCity: {
name: 'Екатеринбург',
code: 'SVX',
location: {
lat: 56.7365,
lon: 60.8025,
},
},
flightCount: 3,
directFlightCount: 2,
},
{
arrivalCity: {
name: 'Новосибирск',
code: 'OVB',
location: {
lat: 55.0077,
lon: 82.9484,
},
},
flightCount: 4,
directFlightCount: 1,
},
],
},
};
const mockNearbyAirports = {
data: {
airports: [
{
code: 'SVO',
name: 'Шереметьево',
location: {
lat: 55.9728,
lon: 37.4146,
},
},
{
code: 'VKO',
name: 'Внуково',
location: {
lat: 55.5917,
lon: 37.2656,
},
},
],
},
};
beforeEach(() => {
cy.intercept(
'GET',
'**/api/flights/destinations/**',
mockDestinations
).as('getDestinations');
cy.intercept(
'GET',
'**/api/flights/nearby/**',
mockNearbyAirports
).as('getNearby');
cy.forbidGeolocation();
cy.visit('/flights-map');
cy.wait('@getDestinations');
});
// ======================================
// MAP RENDERING TESTS (~15 tests)
// ======================================
describe('Map Rendering', () => {
it('should load map and be interactive', () => {
cy.get('#map').should('be.visible');
cy.get('.leaflet-container').should('be.visible');
});
it('should display map with correct base tile layer', () => {
cy.get('.leaflet-tile-pane').should('be.visible');
cy.get('.leaflet-tile').should('have.length.greaterThan', 0);
});
it('should render flight destination markers on map', () => {
cy.get('[data-testid="map-marker"]').should('have.length.greaterThan', 0);
});
it('should display markers for all destination routes', () => {
const expectedMarkerCount = mockDestinations.data.routes.length;
cy.get('[data-testid="map-marker"]').should('have.length', expectedMarkerCount);
});
it('should have correct marker positions based on destination coordinates', () => {
cy.get('[data-testid="map-marker"]').first().should('have.attr', 'data-lat');
cy.get('[data-testid="map-marker"]').first().should('have.attr', 'data-lon');
});
it('should support map pan functionality', () => {
cy.get('.leaflet-container')
.trigger('mousedown', { x: 400, y: 300 })
.trigger('mousemove', { x: 300, y: 300 })
.trigger('mouseup');
// Verify map content changed (panned)
cy.get('.leaflet-tile').should('exist');
});
it('should support map zoom in', () => {
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: -100 });
cy.get('[data-testid="leaflet-map"]')
.invoke('getZoom')
.should('be.greaterThan', 5);
});
it('should support map zoom out', () => {
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: 100 });
cy.get('[data-testid="leaflet-map"]')
.invoke('getZoom')
.should('be.lessThan', 6);
});
it('should respect min and max zoom levels', () => {
cy.get('[data-testid="leaflet-map"]')
.invoke('getMinZoom')
.should('equal', 3);
cy.get('[data-testid="leaflet-map"]')
.invoke('getMaxZoom')
.should('equal', 6);
});
it('should display geolocation button (if available)', () => {
cy.get('[data-testid="geolocation-button"]').should('exist');
});
it('should have geolocation button disabled when geolocation is forbidden', () => {
cy.get('[data-testid="geolocation-button"]').should('be.disabled');
});
it('should render map container with correct CSS classes', () => {
cy.get('[data-testid="flights-map-container"]').should('have.class', 'map-wrapper');
});
it('should display map with proper sizing', () => {
cy.get('[data-testid="flights-map-container"]').should('be.visible');
cy.get('#map').should('have.css', 'position', 'relative');
});
it('should not show loader after map loads', () => {
cy.get('[data-testid="loader"]').should('not.exist');
});
it('should display destination markers with distinct styling', () => {
cy.get('[data-testid="map-marker"]').each(($marker) => {
cy.wrap($marker).should('have.css', 'opacity', '1');
});
});
});
// ======================================
// DESTINATION LIST TESTS (~15 tests)
// ======================================
describe('Destination List', () => {
it('should render destination list panel', () => {
cy.get('[data-testid="destination-list"]').should('be.visible');
});
it('should display all destinations in the list', () => {
const expectedCount = mockDestinations.data.routes.length;
cy.get('[data-testid="destination-list-item"]').should('have.length', expectedCount);
});
it('should display destination name in list items', () => {
cy.get('[data-testid="destination-list-item"]').first()
.should('contain', mockDestinations.data.routes[0].arrivalCity.name);
});
it('should display destination code in list items', () => {
cy.get('[data-testid="destination-list-item"]').first()
.should('contain', mockDestinations.data.routes[0].arrivalCity.code);
});
it('should display flight count in list items', () => {
cy.get('[data-testid="destination-list-item"]').first()
.should('contain', mockDestinations.data.routes[0].flightCount);
});
it('should display direct flight count in list items', () => {
cy.get('[data-testid="destination-list-item"]').first()
.should('contain', mockDestinations.data.routes[0].directFlightCount);
});
it('should render search/filter input for destinations', () => {
cy.get('[data-testid="destination-search-input"]').should('be.visible');
});
it('should filter destination list by city name', () => {
cy.get('[data-testid="destination-search-input"]')
.type('Москва');
cy.get('[data-testid="destination-list-item"]').should('have.length', 1);
cy.get('[data-testid="destination-list-item"]').should('contain', 'Москва');
});
it('should filter destination list by city code', () => {
cy.get('[data-testid="destination-search-input"]')
.type('MOW');
cy.get('[data-testid="destination-list-item"]').should('have.length', 1);
cy.get('[data-testid="destination-list-item"]').should('contain', 'MOW');
});
it('should show empty state when no destinations match filter', () => {
cy.get('[data-testid="destination-search-input"]')
.type('NONEXISTENT');
cy.get('[data-testid="destination-list-empty"]').should('be.visible');
cy.get('[data-testid="destination-list-item"]').should('have.length', 0);
});
it('should clear filter when search input is cleared', () => {
cy.get('[data-testid="destination-search-input"]')
.type('Москва');
cy.get('[data-testid="destination-list-item"]').should('have.length', 1);
cy.get('[data-testid="destination-search-input"]').clear();
cy.get('[data-testid="destination-list-item"]').should('have.length', 5);
});
it('should have list items with proper styling', () => {
cy.get('[data-testid="destination-list-item"]').first()
.should('have.css', 'cursor', 'pointer');
});
it('should show list item hover effect', () => {
cy.get('[data-testid="destination-list-item"]').first()
.trigger('mouseenter')
.should('have.class', 'hover');
});
it('should render list with scrollable container if needed', () => {
cy.get('[data-testid="destination-list"]').should('exist');
cy.get('[data-testid="destination-list"]').invoke('attr', 'class')
.should('include', 'scrollable');
});
it('should display destination icons in list items', () => {
cy.get('[data-testid="destination-list-item"]').first()
.find('[data-testid="destination-icon"]').should('exist');
});
});
// ======================================
// MAP INTERACTIONS TESTS (~10 tests)
// ======================================
describe('Map Interactions', () => {
it('should show popup when clicking on marker', () => {
cy.get('[data-testid="map-marker"]').first().click();
cy.get('[data-testid="map-popup"]').should('be.visible');
});
it('should display destination name in popup', () => {
cy.get('[data-testid="map-marker"]').first().click();
cy.get('[data-testid="map-popup"]')
.should('contain', mockDestinations.data.routes[0].arrivalCity.name);
});
it('should display flight count in popup', () => {
cy.get('[data-testid="map-marker"]').first().click();
cy.get('[data-testid="map-popup"]')
.should('contain', mockDestinations.data.routes[0].flightCount);
});
it('should display link to search flights in popup', () => {
cy.get('[data-testid="map-marker"]').first().click();
cy.get('[data-testid="map-popup"]')
.find('[data-testid="popup-search-link"]')
.should('exist');
});
it('should close popup when clicking outside', () => {
cy.get('[data-testid="map-marker"]').first().click();
cy.get('[data-testid="map-popup"]').should('be.visible');
cy.get('#map').click(100, 100);
cy.get('[data-testid="map-popup"]').should('not.exist');
});
it('should highlight destination when clicking list item', () => {
cy.get('[data-testid="destination-list-item"]').first().click();
cy.get('[data-testid="map-marker"]').first()
.should('have.class', 'highlighted');
});
it('should center map on marker when clicking destination list item', () => {
const targetCity = mockDestinations.data.routes[0].arrivalCity;
const expectedLat = targetCity.location.lat;
const expectedLon = targetCity.location.lon;
cy.get('[data-testid="destination-list-item"]').first().click();
cy.get('[data-testid="leaflet-map"]')
.invoke('getCenter')
.then((center) => {
expect(Math.round(center.lat)).to.equal(Math.round(expectedLat));
expect(Math.round(center.lng)).to.equal(Math.round(expectedLon));
});
});
it('should highlight marker when hovering over list item', () => {
cy.get('[data-testid="destination-list-item"]').first()
.trigger('mouseenter');
cy.get('[data-testid="map-marker"]').first()
.should('have.class', 'hovered');
});
it('should remove highlight when leaving list item hover', () => {
cy.get('[data-testid="destination-list-item"]').first()
.trigger('mouseenter')
.trigger('mouseleave');
cy.get('[data-testid="map-marker"]').first()
.should('not.have.class', 'hovered');
});
it('should allow clicking popup search link to navigate to search', () => {
cy.get('[data-testid="map-marker"]').first().click();
cy.get('[data-testid="map-popup"]')
.find('[data-testid="popup-search-link"]')
.should('have.attr', 'href');
});
});
// ======================================
// MARKER CLUSTERING TESTS (~5 tests)
// ======================================
describe('Marker Clustering', () => {
it('should not cluster markers at default zoom level', () => {
cy.get('[data-testid="map-marker"]').should('have.length', 5);
});
it('should cluster markers when zooming out below threshold', () => {
// Zoom out significantly
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: 200 });
// Verify zoom is at minimum
cy.get('[data-testid="leaflet-map"]')
.invoke('getZoom')
.should('equal', 3);
});
it('should uncluster markers when zooming in', () => {
// First zoom out
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: 200 });
// Then zoom in
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: -100 });
cy.get('[data-testid="map-marker"]').should('have.length.greaterThan', 0);
});
it('should display cluster count when markers are grouped', () => {
// Zoom out to trigger clustering
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: 200 });
// Check for cluster elements
cy.get('[data-testid="marker-cluster"]').should('exist');
});
it('should expand cluster on click', () => {
cy.get('[data-testid="leaflet-map"]')
.trigger('wheel', { deltaY: 200 });
cy.get('[data-testid="marker-cluster"]').first().click();
// Verify map zoomed in
cy.get('[data-testid="leaflet-map"]')
.invoke('getZoom')
.should('be.greaterThan', 3);
});
});
// ======================================
// GEOLOCATION TESTS (~5 tests)
// ======================================
describe('Geolocation Feature', () => {
it('should disable geolocation button when permission denied', () => {
cy.get('[data-testid="geolocation-button"]').should('be.disabled');
});
it('should show tooltip on geolocation button', () => {
cy.get('[data-testid="geolocation-button"]')
.should('have.attr', 'title');
});
it('should enable geolocation button with valid coordinates', () => {
cy.mockGeolocation({ latitude: 55.7558, longitude: 37.6173 });
cy.reload();
cy.wait('@getDestinations');
cy.get('[data-testid="geolocation-button"]').should('not.be.disabled');
});
it('should center map on user location when geolocation is enabled', () => {
cy.mockGeolocation({ latitude: 55.7558, longitude: 37.6173 });
cy.reload();
cy.wait('@getDestinations');
cy.get('[data-testid="geolocation-button"]').click();
cy.get('[data-testid="leaflet-map"]')
.invoke('getCenter')
.then((center) => {
expect(Math.round(center.lat)).to.equal(56);
expect(Math.round(center.lng)).to.equal(38);
});
});
it('should show user location marker on map', () => {
cy.mockGeolocation({ latitude: 55.7558, longitude: 37.6173 });
cy.reload();
cy.wait('@getDestinations');
cy.get('[data-testid="geolocation-button"]').click();
cy.get('[data-testid="user-location-marker"]').should('exist');
});
});
// ======================================
// RESPONSIVE DESIGN TESTS (~5 tests)
// ======================================
describe('Responsive Design', () => {
it('should display map in desktop viewport', () => {
cy.viewport(1280, 720);
cy.get('[data-testid="flights-map-container"]').should('be.visible');
cy.get('[data-testid="destination-list"]').should('be.visible');
});
it('should adapt layout for tablet viewport', () => {
cy.viewport('ipad-2');
cy.get('[data-testid="flights-map-container"]').should('be.visible');
});
it('should adapt layout for mobile viewport', () => {
cy.viewport('iphone-x');
cy.get('[data-testid="flights-map-container"]').should('be.visible');
});
it('should show mobile-friendly destination list on small screens', () => {
cy.viewport('iphone-x');
cy.get('[data-testid="destination-list"]').should('be.visible');
});
it('should adjust map controls for mobile devices', () => {
cy.viewport('iphone-x');
cy.get('[data-testid="geolocation-button"]').should('be.visible');
cy.get('.leaflet-control-container').should('be.visible');
});
});
// ======================================
// API INTEGRATION TESTS (~5 tests)
// ======================================
describe('API Integration', () => {
it('should fetch destinations on page load', () => {
cy.get('@getDestinations').should('have.been.called');
});
it('should handle API errors gracefully', () => {
cy.intercept(
'GET',
'**/api/flights/destinations/**',
{ statusCode: 500 }
).as('getDestinationsError');
cy.reload();
cy.wait('@getDestinationsError');
cy.get('[data-testid="error-message"]').should('be.visible');
});
it('should retry failed API requests', () => {
cy.intercept(
'GET',
'**/api/flights/destinations/**',
{ statusCode: 500 }
).as('getDestinationsError');
cy.reload();
cy.wait('@getDestinationsError');
cy.get('[data-testid="retry-button"]').click();
cy.intercept(
'GET',
'**/api/flights/destinations/**',
mockDestinations
).as('getDestinationsRetry');
cy.wait('@getDestinationsRetry');
});
it('should fetch nearby airports when geolocation enabled', () => {
cy.mockGeolocation({ latitude: 55.7558, longitude: 37.6173 });
cy.reload();
cy.wait('@getDestinations');
cy.get('[data-testid="geolocation-button"]').click();
cy.get('@getNearby').should('exist');
});
it('should update map when destinations data changes', () => {
const updatedDestinations = {
data: {
routes: [
{
arrivalCity: {
name: 'Казань',
code: 'KZN',
location: {
lat: 55.6084,
lon: 49.2808,
},
},
flightCount: 2,
directFlightCount: 1,
},
],
},
};
cy.intercept(
'GET',
'**/api/flights/destinations/**',
updatedDestinations
).as('getUpdatedDestinations');
cy.reload();
cy.wait('@getUpdatedDestinations');
cy.get('[data-testid="map-marker"]').should('have.length', 1);
});
});
// ======================================
// FILTER STATE PERSISTENCE TESTS (~3 tests)
// ======================================
describe('Filter State Persistence', () => {
it('should retain destination search filter on page reload', () => {
cy.get('[data-testid="destination-search-input"]')
.type('Москва');
cy.get('[data-testid="destination-list-item"]').should('have.length', 1);
cy.reload();
cy.wait('@getDestinations');
// Filter should be retained (depends on implementation)
cy.get('[data-testid="destination-search-input"]')
.invoke('val')
.then((val) => {
// Verify value is retained
expect(val).to.be.a('string');
});
});
it('should retain map center position on navigation', () => {
const targetCity = mockDestinations.data.routes[0].arrivalCity;
cy.get('[data-testid="destination-list-item"]').first().click();
cy.visit('/flights-map');
cy.wait('@getDestinations');
// Verify map state is reasonable
cy.get('[data-testid="leaflet-map"]').should('be.visible');
});
it('should preserve marker highlight state during interactions', () => {
cy.get('[data-testid="destination-list-item"]').first().click();
cy.get('[data-testid="map-marker"]').first()
.should('have.class', 'highlighted');
// Interact with another destination
cy.get('[data-testid="destination-list-item"]').eq(1).click();
// First marker should no longer be highlighted
cy.get('[data-testid="destination-list-item"]').eq(1)
.scrollIntoView();
cy.get('[data-testid="map-marker"]').eq(1)
.should('have.class', 'highlighted');
});
});
});