663 lines
24 KiB
TypeScript
663 lines
24 KiB
TypeScript
/// <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');
|
|
});
|
|
});
|
|
});
|