diff --git a/ClientApp/cypress/integration/features/online-board.cy.ts b/ClientApp/cypress/integration/features/online-board.cy.ts new file mode 100644 index 00000000..92830c95 --- /dev/null +++ b/ClientApp/cypress/integration/features/online-board.cy.ts @@ -0,0 +1,973 @@ +import * as moment from 'moment'; +import { CITIES, MOCK_FLIGHTS_ARRIVAL, MOCK_FLIGHTS_DEPARTURE } from '../../support/fixtures'; + +describe('Online Board Feature Tests (~70 tests)', () => { + const today = moment().format('DD.MM.YYYY'); + const tomorrow = moment().add(1, 'day').format('DD.MM.YYYY'); + const yesterday = moment().subtract(1, 'day').format('DD.MM.YYYY'); + const nextWeek = moment().add(7, 'day').format('DD.MM.YYYY'); + + const expectedUrlDateTime = `${moment().format('DDMMYYYY')}-0000-2400`; + + beforeEach(() => { + cy.intercept('GET', '**/api/flights/v1.1/**').as('getFlights'); + cy.intercept('GET', '**/api/cities/**').as('getCities'); + cy.forbidGeolocation(); + cy.visit('/'); + }); + + // ============================================================================ + // ARRIVAL TAB TESTS (~20 tests) + // ============================================================================ + describe('Arrival Tab Tests', () => { + describe('City Input - Manual Entry', () => { + it('should accept manual city entry for valid city name', () => { + cy.getByTestId('city-autocomplete-input-arrival') + .clear() + .type('Москва'); + cy.getByTestId('city-autocomplete-input-arrival') + .should('have.value', 'Москва'); + }); + + it('should display dropdown suggestions for partial city name', () => { + cy.getByTestId('city-autocomplete-input-arrival') + .clear() + .type('Мос'); + cy.getByTestId('city-dropdown-option') + .should('be.visible') + .should('have.length.greaterThan', 0); + }); + + it('should filter dropdown options based on input', () => { + cy.getByTestId('city-autocomplete-input-arrival') + .clear() + .type('Анапа'); + cy.getByTestId('city-dropdown-option') + .contains('Анапа') + .should('be.visible'); + }); + + it('should handle special characters in city input', () => { + cy.getByTestId('city-autocomplete-input-arrival') + .clear() + .type('М@сква'); + // Should not crash and handle gracefully + cy.getByTestId('city-autocomplete-input-arrival').should('exist'); + }); + + it('should clear city input when cleared explicitly', () => { + cy.getByTestId('city-autocomplete-input-arrival') + .clear() + .type('Москва'); + cy.getByTestId('city-autocomplete-input-arrival').clear(); + cy.getByTestId('city-autocomplete-input-arrival') + .should('have.value', ''); + }); + + it('should show validation error for empty city input on search', () => { + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('City'); + }); + }); + + describe('City Input - Dropdown Selection', () => { + it('should select city from dropdown by clicking', () => { + cy.getByTestId('city-autocomplete-input-arrival') + .clear() + .type('Москва'); + cy.getByTestId('city-dropdown-option') + .contains('Москва') + .click(); + cy.getByTestId('city-autocomplete-input-arrival') + .should('have.value', 'Москва'); + }); + + it('should display city code after selection from dropdown', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('city-code') + .should('contain', 'MOW'); + }); + + it('should allow switching between different cities using dropdown', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('city-code').should('contain', 'MOW'); + + cy.getByTestId('city-autocomplete-input-arrival').clear().type('Анапа'); + cy.getByTestId('city-dropdown-option').contains('Анапа').click(); + cy.getByTestId('city-code').should('contain', 'AAQ'); + }); + }); + + describe('Date Picker - Valid Dates', () => { + it('should accept valid today date', () => { + cy.getByTestId('arrival-date-input') + .clear() + .type(today) + .type('{enter}'); + cy.getByTestId('arrival-date-input') + .should('have.value', today); + }); + + it('should accept valid future date (tomorrow)', () => { + cy.getByTestId('arrival-date-input') + .clear() + .type(tomorrow) + .type('{enter}'); + cy.getByTestId('arrival-date-input') + .should('have.value', tomorrow); + }); + + it('should accept valid future date (one week)', () => { + cy.getByTestId('arrival-date-input') + .clear() + .type(nextWeek) + .type('{enter}'); + cy.getByTestId('arrival-date-input') + .should('have.value', nextWeek); + }); + }); + + describe('Date Picker - Invalid Dates', () => { + it('should reject past date (yesterday)', () => { + cy.getByTestId('arrival-date-input') + .clear() + .type(yesterday); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('date'); + }); + + it('should handle invalid date format', () => { + cy.getByTestId('arrival-date-input') + .clear() + .type('invalid'); + cy.getByTestId('search-button').click(); + // Should show error or ignore invalid input + cy.getByTestId('validation-error').should('exist'); + }); + + it('should show validation error when date field is empty on search', () => { + cy.getByTestId('arrival-date-input').clear(); + cy.selectArrivalCity('Москва'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('date'); + }); + }); + + describe('Search - Valid and Error Cases', () => { + it('should perform valid arrival search with city and date', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.getByTestId('loader').should('be.visible'); + cy.wait('@getFlights').then(() => { + cy.getByTestId('board-search-result').should('be.visible'); + }); + }); + + it('should show validation error when missing city field', () => { + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('City'); + }); + + it('should show validation error when missing date field', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('date'); + }); + + it('should handle network error gracefully', () => { + cy.intercept('GET', '**/api/flights/v1.1/**', { + statusCode: 500, + body: { error: 'Internal Server Error' }, + }).as('getFlightsError'); + + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlightsError'); + cy.getByTestId('error-message').should('be.visible'); + }); + + it('should show loading state during search', () => { + cy.intercept('GET', '**/api/flights/v1.1/**', (req) => { + req.reply((res) => { + res.delay(1000); + }); + }).as('getFlightsSlow'); + + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.getByTestId('loader').should('be.visible'); + cy.wait('@getFlightsSlow'); + }); + }); + + describe('Results - Flight List Rendering', () => { + it('should render flight list after successful search', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().should('have.length.greaterThan', 0); + }); + + it('should display all required flight information in results', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().within(() => { + cy.getByTestId('flight-carrier-number').should('be.visible'); + cy.getByTestId('flight-status').should('be.visible'); + cy.getByTestId('flight-time').should('be.visible'); + }); + }); + }); + + describe('Results - Flight Details Modal', () => { + it('should open flight details modal on flight click', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + }); + + it('should display all flight info in modal (number, times, gate, terminal)', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-number').should('be.visible'); + cy.getByTestId('flight-details-time').should('be.visible'); + cy.getByTestId('flight-details-gate').should('be.visible'); + cy.getByTestId('flight-details-terminal').should('be.visible'); + }); + + it('should close modal when clicking X button', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.getByTestId('modal-close-button').click(); + cy.getByTestId('flight-details-modal').should('not.be.visible'); + }); + + it('should close modal when pressing Escape key', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.get('body').type('{esc}'); + cy.getByTestId('flight-details-modal').should('not.be.visible'); + }); + + it('should close modal when clicking outside modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.getByTestId('modal-backdrop').click({ force: true }); + cy.getByTestId('flight-details-modal').should('not.be.visible'); + }); + }); + + describe('Filter Persistence', () => { + it('should preserve arrival filters when navigating back', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + // Navigate back + cy.go('back'); + + // Filters should still be present + cy.getByTestId('city-autocomplete-input-arrival').should('have.value', 'Москва'); + cy.getByTestId('arrival-date-input').should('have.value', today); + }); + }); + }); + + // ============================================================================ + // DEPARTURE TAB TESTS (~20 tests) + // ============================================================================ + describe('Departure Tab Tests', () => { + beforeEach(() => { + cy.getByTestId('departure-tab').click(); + }); + + describe('City Input - Manual Entry', () => { + it('should accept manual city entry for departure', () => { + cy.getByTestId('city-autocomplete-input-departure') + .clear() + .type('Москва'); + cy.getByTestId('city-autocomplete-input-departure') + .should('have.value', 'Москва'); + }); + + it('should display dropdown suggestions for departure city', () => { + cy.getByTestId('city-autocomplete-input-departure') + .clear() + .type('Мос'); + cy.getByTestId('city-dropdown-option') + .should('be.visible'); + }); + + it('should filter dropdown options for departure based on input', () => { + cy.getByTestId('city-autocomplete-input-departure') + .clear() + .type('Казань'); + cy.getByTestId('city-dropdown-option') + .contains('Казань') + .should('be.visible'); + }); + + it('should show validation error for empty departure city on search', () => { + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('City'); + }); + }); + + describe('City Input - Dropdown Selection', () => { + it('should select departure city from dropdown', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('city-autocomplete-input-departure') + .should('have.value', 'Москва'); + }); + + it('should display departure city code after selection', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('city-code').should('contain', 'MOW'); + }); + + it('should allow switching between different departure cities', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('city-code').should('contain', 'MOW'); + + cy.getByTestId('city-autocomplete-input-departure').clear().type('Казань'); + cy.getByTestId('city-dropdown-option').contains('Казань').click(); + cy.getByTestId('city-code').should('contain', 'KZN'); + }); + }); + + describe('Date Picker - Valid Dates', () => { + it('should accept valid today date for departure', () => { + cy.getByTestId('departure-date-input') + .clear() + .type(today) + .type('{enter}'); + cy.getByTestId('departure-date-input') + .should('have.value', today); + }); + + it('should accept valid future date for departure', () => { + cy.getByTestId('departure-date-input') + .clear() + .type(tomorrow) + .type('{enter}'); + cy.getByTestId('departure-date-input') + .should('have.value', tomorrow); + }); + }); + + describe('Date Picker - Invalid Dates', () => { + it('should reject past date for departure', () => { + cy.getByTestId('departure-date-input') + .clear() + .type(yesterday); + cy.selectDepartureCity('Москва'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('date'); + }); + + it('should show validation error when departure date is empty on search', () => { + cy.getByTestId('departure-date-input').clear(); + cy.selectDepartureCity('Москва'); + cy.getByTestId('search-button').click(); + cy.shouldShowValidationError('date'); + }); + }); + + describe('Search - Valid and Error Cases', () => { + it('should perform valid departure search', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.getByTestId('loader').should('be.visible'); + cy.wait('@getFlights').then(() => { + cy.getByTestId('board-search-result').should('be.visible'); + }); + }); + + it('should handle network error for departure search', () => { + cy.intercept('GET', '**/api/flights/v1.1/**', { + statusCode: 500, + }).as('getFlightsError'); + + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlightsError'); + cy.getByTestId('error-message').should('be.visible'); + }); + + it('should show loading state during departure search', () => { + cy.intercept('GET', '**/api/flights/v1.1/**', (req) => { + req.reply((res) => { + res.delay(1000); + }); + }).as('getFlightsSlow'); + + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.getByTestId('loader').should('be.visible'); + }); + }); + + describe('Results - Flight List', () => { + it('should render departure flight list after successful search', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().should('have.length.greaterThan', 0); + }); + + it('should display required flight information in departure results', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().within(() => { + cy.getByTestId('flight-carrier-number').should('be.visible'); + cy.getByTestId('flight-status').should('be.visible'); + }); + }); + }); + + describe('Results - Flight Details Modal for Departure', () => { + it('should open flight details modal for departure flight', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + }); + + it('should display complete flight details for departure', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-number').should('be.visible'); + cy.getByTestId('flight-details-gate').should('be.visible'); + cy.getByTestId('flight-details-terminal').should('be.visible'); + }); + + it('should close departure flight details modal on X click', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.getByTestId('modal-close-button').click(); + cy.getByTestId('flight-details-modal').should('not.be.visible'); + }); + }); + + describe('Filter Persistence for Departure', () => { + it('should preserve departure filters when navigating back', () => { + cy.selectDepartureCity('Москва'); + cy.getByTestId('departure-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.go('back'); + + cy.getByTestId('city-autocomplete-input-departure').should('have.value', 'Москва'); + cy.getByTestId('departure-date-input').should('have.value', today); + }); + }); + }); + + // ============================================================================ + // TAB SWITCHING TESTS (~5 tests) + // ============================================================================ + describe('Tab Switching Tests', () => { + it('should switch from arrival tab to departure tab', () => { + cy.getByTestId('arrival-tab').should('have.class', 'active'); + cy.getByTestId('departure-tab').click(); + cy.getByTestId('departure-tab').should('have.class', 'active'); + }); + + it('should switch from departure tab back to arrival tab', () => { + cy.getByTestId('departure-tab').click(); + cy.getByTestId('departure-tab').should('have.class', 'active'); + cy.getByTestId('arrival-tab').click(); + cy.getByTestId('arrival-tab').should('have.class', 'active'); + }); + + it('should maintain separate state for arrival and departure tabs', () => { + // Set arrival filter + cy.selectArrivalCity('Москва'); + cy.getByTestId('city-autocomplete-input-arrival').should('have.value', 'Москва'); + + // Switch to departure + cy.getByTestId('departure-tab').click(); + cy.getByTestId('city-autocomplete-input-departure').should('have.value', ''); + + // Switch back to arrival + cy.getByTestId('arrival-tab').click(); + cy.getByTestId('city-autocomplete-input-arrival').should('have.value', 'Москва'); + }); + + it('should preserve departure state when switching tabs', () => { + cy.getByTestId('departure-tab').click(); + cy.selectDepartureCity('Казань'); + cy.getByTestId('city-autocomplete-input-departure').should('have.value', 'Казань'); + + cy.getByTestId('arrival-tab').click(); + cy.getByTestId('departure-tab').click(); + + cy.getByTestId('city-autocomplete-input-departure').should('have.value', 'Казань'); + }); + }); + + // ============================================================================ + // FLIGHT NUMBER FILTER TESTS (~15 tests) + // ============================================================================ + describe('Flight Number Filter Tests', () => { + describe('Basic Flight Number Filtering', () => { + it('should filter results by flight number', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('SU001'); + + cy.getFlightResults().should('have.length', 1); + cy.getFirstFlightResult().should('contain', 'SU001'); + }); + + it('should filter flights by partial flight number', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('001'); + + cy.getFlightResults().should('have.length', 1); + }); + + it('should handle no results when filtering by non-existent flight number', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('ZZ999'); + + cy.getByTestId('no-results-message').should('be.visible'); + cy.getFlightResults().should('have.length', 0); + }); + + it('should be case-insensitive when filtering flight numbers', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('su001'); + + cy.getFlightResults().should('have.length', 1); + cy.getFirstFlightResult().should('contain', 'SU001'); + }); + }); + + describe('Flight Number Filter - Special Characters', () => { + it('should handle special characters in flight number filter gracefully', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('SU@001'); + + // Should not crash, display no results or handle gracefully + cy.getByTestId('flight-number-filter').should('exist'); + }); + + it('should handle empty flight number filter (no filter applied)', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().should('have.length.greaterThan', 0); + }); + + it('should ignore leading/trailing spaces in flight number filter', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type(' SU001 '); + + cy.getFlightResults().should('have.length', 1); + }); + }); + + describe('Flight Number Filter - Clear Filter', () => { + it('should clear flight number filter', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().then((flights) => { + const initialCount = flights.length; + + cy.getByTestId('flight-number-filter').clear().type('SU001'); + cy.getFlightResults().should('have.length', 1); + + cy.getByTestId('flight-number-filter').clear(); + cy.getFlightResults().should('have.length', initialCount); + }); + }); + + it('should reset filter when clicking clear button', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('SU001'); + cy.getFlightResults().should('have.length', 1); + + cy.getByTestId('clear-flight-filter-button').click(); + cy.getByTestId('flight-number-filter').should('have.value', ''); + cy.getFlightResults().should('have.length.greaterThan', 1); + }); + + it('should update results in real-time as user types in flight number filter', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().then((flights) => { + const initialCount = flights.length; + + cy.getByTestId('flight-number-filter').type('0'); + cy.getFlightResults().should('have.length.lessThan', initialCount); + + cy.getByTestId('flight-number-filter').type('01'); + cy.getFlightResults().should('have.length', 1); + }); + }); + }); + + describe('Flight Number Filter - Integration with Other Filters', () => { + it('should combine flight number filter with date filter', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().should('have.length.greaterThan', 0); + + cy.getByTestId('flight-number-filter').clear().type('SU001'); + cy.getFlightResults().should('have.length', 1); + + // Change date and verify filter still works + cy.getByTestId('arrival-date-input').clear().type(tomorrow).type('{enter}'); + cy.getByTestId('flight-number-filter').should('have.value', 'SU001'); + }); + + it('should preserve flight number filter when switching between tabs', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getByTestId('flight-number-filter').clear().type('SU001'); + + cy.getByTestId('departure-tab').click(); + cy.getByTestId('arrival-tab').click(); + + // Filter might not persist across tabs, but should not crash + cy.getByTestId('flight-number-filter').should('exist'); + }); + }); + }); + + // ============================================================================ + // FLIGHT DETAILS MODAL TESTS (~15 tests) + // ============================================================================ + describe('Flight Details Modal Tests', () => { + describe('Modal Opening and Closing', () => { + it('should open modal when clicking on flight result', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + }); + + it('should close modal with close button (X)', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.getByTestId('modal-close-button').click(); + cy.getByTestId('flight-details-modal').should('not.exist'); + }); + + it('should close modal when pressing Escape key', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.get('body').type('{esc}'); + cy.getByTestId('flight-details-modal').should('not.exist'); + }); + + it('should close modal when clicking outside (backdrop)', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + + cy.getByTestId('modal-backdrop').click({ force: true }); + cy.getByTestId('flight-details-modal').should('not.exist'); + }); + }); + + describe('Modal Content - Flight Information Display', () => { + it('should display flight number in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-number').should('be.visible') + .should('contain', 'SU'); + }); + + it('should display estimated arrival time in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-time').should('be.visible'); + }); + + it('should display gate information in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-gate').should('be.visible') + .should('contain', 'Gate'); + }); + + it('should display terminal information in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-terminal').should('be.visible') + .should('contain', 'Terminal'); + }); + + it('should display flight status in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-status').should('be.visible'); + }); + + it('should display aircraft type in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-aircraft').should('be.visible'); + }); + }); + + describe('Modal Navigation', () => { + it('should navigate to next flight using next button in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + const firstFlightNumber = cy.getByTestId('flight-details-number'); + cy.getByTestId('modal-next-button').click(); + + cy.getByTestId('flight-details-number') + .should('not.equal', firstFlightNumber); + }); + + it('should navigate to previous flight using prev button in modal', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().then((flights) => { + if (flights.length > 1) { + cy.getByTestId('flight-result').eq(1).click(); + cy.getByTestId('modal-prev-button').click(); + cy.getByTestId('flight-details-modal').should('be.visible'); + } + }); + }); + + it('should disable prev button on first flight', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('modal-prev-button').should('be.disabled'); + }); + + it('should disable next button on last flight', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFlightResults().then((flights) => { + cy.getByTestId('flight-result').eq(flights.length - 1).click(); + cy.getByTestId('modal-next-button').should('be.disabled'); + }); + }); + }); + + describe('Modal Display and Responsiveness', () => { + it('should center modal on screen', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.getByTestId('flight-details-modal').should('be.visible'); + cy.getByTestId('flight-details-modal') + .should('have.css', 'position') + .and('match', /absolute|fixed/); + }); + + it('should prevent scrolling on body when modal is open', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + + cy.get('body').should('have.css', 'overflow', 'hidden'); + }); + + it('should restore body scrolling when modal closes', () => { + cy.selectArrivalCity('Москва'); + cy.getByTestId('arrival-date-input').clear().type(today).type('{enter}'); + cy.getByTestId('search-button').click(); + + cy.wait('@getFlights'); + cy.getFirstFlightResult().click(); + cy.getByTestId('modal-close-button').click(); + + cy.get('body').should('not.have.css', 'overflow', 'hidden'); + }); + }); + }); +}); diff --git a/ClientApp/cypress/integration/features/schedule.cy.ts b/ClientApp/cypress/integration/features/schedule.cy.ts new file mode 100644 index 00000000..0c773976 --- /dev/null +++ b/ClientApp/cypress/integration/features/schedule.cy.ts @@ -0,0 +1,640 @@ +import * as moment from 'moment'; + +/** + * Mock schedule results for testing + */ +const MOCK_SCHEDULE_RESULTS = [ + { + flightNumber: 'SU1001', + carrier: 'SU', + number: '1001', + departure: 'Москва', + departureCode: 'MOW', + arrival: 'Санкт-Петербург', + arrivalCode: 'LED', + departureTime: '09:00', + arrivalTime: '10:30', + duration: '1h 30m', + aircraft: 'A320', + price: 3500, + stops: 0, + operating: 'SU', + flightStatus: 'On Schedule', + }, + { + flightNumber: 'SU1002', + carrier: 'SU', + number: '1002', + departure: 'Москва', + departureCode: 'MOW', + arrival: 'Санкт-Петербург', + arrivalCode: 'LED', + departureTime: '12:15', + arrivalTime: '13:45', + duration: '1h 30m', + aircraft: 'A330', + price: 4200, + stops: 0, + operating: 'SU', + flightStatus: 'On Schedule', + }, + { + flightNumber: 'SU1003', + carrier: 'SU', + number: '1003', + departure: 'Москва', + departureCode: 'MOW', + arrival: 'Санкт-Петербург', + arrivalCode: 'LED', + departureTime: '15:30', + arrivalTime: '17:00', + duration: '1h 30m', + aircraft: 'B737', + price: 2800, + stops: 0, + operating: 'SU', + flightStatus: 'On Schedule', + }, + { + flightNumber: 'SU1004', + carrier: 'SU', + number: '1004', + departure: 'Москва', + departureCode: 'MOW', + arrival: 'Санкт-Петербург', + arrivalCode: 'LED', + departureTime: '18:45', + arrivalTime: '20:15', + duration: '1h 30m', + aircraft: 'A320', + price: 3100, + stops: 0, + operating: 'SU', + flightStatus: 'On Schedule', + }, + { + flightNumber: 'SU1005', + carrier: 'SU', + number: '1005', + departure: 'Москва', + departureCode: 'MOW', + arrival: 'Санкт-Петербург', + arrivalCode: 'LED', + departureTime: '21:00', + arrivalTime: '22:30', + duration: '1h 30m', + aircraft: 'A320', + price: 3000, + stops: 0, + operating: 'SU', + flightStatus: 'On Schedule', + }, +]; + +const MOCK_FLIGHT_DETAILS = { + flightNumber: 'SU1001', + carrier: 'SU', + number: '1001', + departure: 'Москва', + departureCode: 'MOW', + departureTime: '09:00', + departureTerminal: 'A', + departureGate: '5', + departureCheckIn: '07:00-08:45', + arrival: 'Санкт-Петербург', + arrivalCode: 'LED', + arrivalTime: '10:30', + arrivalTerminal: 'B', + arrivalGate: '12', + duration: '1h 30m', + aircraft: 'A320', + aircraftCode: 'A20', + boardingTime: '08:30', + price: 3500, + stops: 0, + operating: 'SU', + flightStatus: 'On Schedule', +}; + +describe('Расписание: Комплексные тесты', () => { + const route = { + departureCity: { + name: 'Москва', + code: 'MOW', + latitude: 55.7558, + longitude: 37.62, + }, + arrivalCity: { + name: 'Санкт-Петербург', + code: 'LED', + latitude: 59.9311, + longitude: 30.3609, + }, + alternateArrivalCity: { + name: 'Сочи', + code: 'AER', + latitude: 43.4391, + longitude: 39.9566, + }, + }; + + beforeEach(() => { + cy.intercept('GET', '**/api/flights/1/ru/schedule**', MOCK_SCHEDULE_RESULTS).as('getSchedule'); + cy.intercept('GET', '**/api/flights/1/ru/schedule/details**', MOCK_FLIGHT_DETAILS).as('getFlightDetails'); + cy.intercept('GET', '**/api/cities/**', { + statusCode: 200, + body: [route.departureCity, route.arrivalCity, route.alternateArrivalCity], + }).as('getCities'); + cy.mockGeolocation(route.departureCity); + cy.visit('/ru-ru/schedule'); + }); + + // ============================================================ + // SEARCH PAGE TESTS (~25 tests) + // ============================================================ + + describe('Search Page - Origin Autocomplete', () => { + it('Should allow manual entry of origin city', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.getByTestId('schedule-departure-city-input').getByTestId('city-code').should('contain', 'MOW'); + }); + + it('Should filter origin cities as user types', () => { + cy.getByTestId('schedule-departure-city-input').type('М'); + cy.getByTestId('city-dropdown-option').should('have.length.at.least', 1); + }); + + it('Should select origin city from dropdown', () => { + cy.getByTestId('schedule-departure-city-input').type('Мо'); + cy.getByTestId('city-dropdown-option').first().click(); + cy.getByTestId('schedule-departure-city-input').getByTestId('city-code').should('contain', 'MOW'); + }); + + it('Should clear origin city selection', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва'); + cy.getByTestId('schedule-departure-city-input').parent().find('[class*="clear"]').click({ force: true }); + cy.getByTestId('schedule-departure-city-input').should('have.value', ''); + }); + + it('Should validate that origin city is required', () => { + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург'); + cy.getByTestId('schedule-search-button').click(); + cy.getByTestId('validation-error').should('be.visible'); + }); + + it('Should display origin city code after selection', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.getByTestId('city-code').contains('MOW').should('be.visible'); + }); + + it('Should handle rapid typing in origin field', () => { + cy.getByTestId('schedule-departure-city-input').type('М', { delay: 10 }).type('о', { delay: 10 }); + cy.getByTestId('city-dropdown-option').should('have.length.at.least', 1); + }); + + it('Should preserve origin city when navigating to details', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + cy.getByTestId('schedule-search-result').first().click(); + cy.url().should('include', 'details'); + }); + }); + + describe('Search Page - Destination Autocomplete', () => { + it('Should allow manual entry of destination city', () => { + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-arrival-city-input').getByTestId('city-code').should('contain', 'LED'); + }); + + it('Should filter destination cities as user types', () => { + cy.getByTestId('schedule-arrival-city-input').type('С'); + cy.getByTestId('city-dropdown-option').should('have.length.at.least', 1); + }); + + it('Should select destination city from dropdown', () => { + cy.getByTestId('schedule-arrival-city-input').type('Са'); + cy.getByTestId('city-dropdown-option').first().click(); + cy.getByTestId('schedule-arrival-city-input').getByTestId('city-code').should('be.visible'); + }); + + it('Should clear destination city selection', () => { + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург'); + cy.getByTestId('schedule-arrival-city-input').parent().find('[class*="clear"]').click({ force: true }); + cy.getByTestId('schedule-arrival-city-input').should('have.value', ''); + }); + + it('Should validate that destination city is required', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва'); + cy.getByTestId('schedule-search-button').click(); + cy.getByTestId('validation-error').should('be.visible'); + }); + + it('Should prevent same city for origin and destination', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Москва').type('{enter}'); + cy.getByTestId('validation-error').should('contain', 'одинаков'); + }); + + it('Should display destination city code after selection', () => { + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('city-code').contains('LED').should('be.visible'); + }); + }); + + describe('Search Page - Date Range Picker', () => { + it('Should set start date using date picker', () => { + const startDate = moment().format('DD.MM.YYYY'); + cy.getByTestId('schedule-calendar').first().clear().type(startDate).type('{enter}'); + cy.getByTestId('schedule-calendar').first().should('have.value', startDate); + }); + + it('Should set end date using date picker', () => { + const endDate = moment().add(7, 'days').format('DD.MM.YYYY'); + cy.getByTestId('schedule-calendar').last().clear().type(endDate).type('{enter}'); + cy.getByTestId('schedule-calendar').last().should('have.value', endDate); + }); + + it('Should allow single-day range', () => { + const singleDate = moment().format('DD.MM.YYYY'); + cy.getByTestId('schedule-calendar').first().clear().type(singleDate).type('{enter}'); + cy.getByTestId('schedule-calendar').last().clear().type(singleDate).type('{enter}'); + cy.getByTestId('schedule-calendar').first().should('have.value', singleDate); + cy.getByTestId('schedule-calendar').last().should('have.value', singleDate); + }); + + it('Should allow full range selection (7 days)', () => { + const startDate = moment().format('DD.MM.YYYY'); + const endDate = moment().add(7, 'days').format('DD.MM.YYYY'); + cy.getByTestId('schedule-calendar').first().clear().type(startDate).type('{enter}'); + cy.getByTestId('schedule-calendar').last().clear().type(endDate).type('{enter}'); + cy.getByTestId('schedule-calendar').first().should('have.value', startDate); + cy.getByTestId('schedule-calendar').last().should('have.value', endDate); + }); + + it('Should reject end date before start date', () => { + const endDate = moment().format('DD.MM.YYYY'); + const startDate = moment().add(7, 'days').format('DD.MM.YYYY'); + cy.getByTestId('schedule-calendar').first().clear().type(startDate).type('{enter}'); + cy.getByTestId('schedule-calendar').last().clear().type(endDate).type('{enter}'); + cy.getByTestId('validation-error').should('be.visible'); + }); + + it('Should use today as default start date', () => { + cy.getByTestId('schedule-calendar').first().should('have.value', moment().format('DD.MM.YYYY')); + }); + + it('Should prevent date in the past', () => { + const pastDate = moment().subtract(1, 'days').format('DD.MM.YYYY'); + cy.getByTestId('schedule-calendar').first().clear().type(pastDate).type('{enter}'); + cy.getByTestId('validation-error').should('be.visible'); + }); + + it('Should allow date selection via calendar popup', () => { + cy.getByTestId('schedule-calendar').first().click(); + cy.get('[class*="calendar"]').find('[class*="day"]').contains(moment().date().toString()).click({ force: true }); + cy.getByTestId('schedule-calendar').first().should('have.value', moment().format('DD.MM.YYYY')); + }); + }); + + describe('Search Page - Form Submission', () => { + it('Should submit valid search form', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + cy.getByTestId('schedule-search-results').should('be.visible'); + }); + + it('Should show loading indicator during search', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.getByTestId('loader').should('be.visible'); + }); + + it('Should display error on network failure', () => { + cy.intercept('GET', '**/api/flights/1/ru/schedule**', { statusCode: 500 }).as('getScheduleError'); + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getScheduleError'); + cy.getByTestId('error-message').should('be.visible'); + }); + + it('Should handle empty search results', () => { + cy.intercept('GET', '**/api/flights/1/ru/schedule**', []).as('getScheduleEmpty'); + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getScheduleEmpty'); + cy.getByTestId('empty-results-message').should('be.visible'); + }); + + it('Should not submit with missing origin city', () => { + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.get('@getSchedule.all').should('have.length', 0); + }); + + it('Should not submit with missing destination city', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.get('@getSchedule.all').should('have.length', 0); + }); + + it('Should display correct URL after search', () => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + cy.url().should('include', 'schedule'); + }); + + it('Should enable search button only when form is valid', () => { + cy.getByTestId('schedule-search-button').should('be.disabled'); + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-search-button').should('be.disabled'); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-search-button').should('be.enabled'); + }); + }); + + // ============================================================ + // FLIGHT DETAILS PAGE TESTS (~20 tests) + // ============================================================ + + describe('Flight Details Page - Flight Information', () => { + beforeEach(() => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + cy.getByTestId('schedule-search-result').first().click(); + cy.wait('@getFlightDetails'); + }); + + it('Should display flight number', () => { + cy.getByTestId('flight-details-number').should('contain', 'SU'); + }); + + it('Should display departure information', () => { + cy.getByTestId('flight-departure-time').should('be.visible'); + cy.getByTestId('flight-departure-city').should('contain', 'Москва'); + }); + + it('Should display arrival information', () => { + cy.getByTestId('flight-arrival-time').should('be.visible'); + cy.getByTestId('flight-arrival-city').should('contain', 'Санкт-Петербург'); + }); + + it('Should display flight duration', () => { + cy.getByTestId('flight-duration').should('contain', 'h'); + }); + + it('Should display aircraft type', () => { + cy.getByTestId('flight-aircraft').should('contain', 'A320'); + }); + + it('Should display airline logo', () => { + cy.getByTestId('flight-company-logo').should('be.visible'); + }); + + it('Should display price information', () => { + cy.getByTestId('flight-price').should('be.visible').should('contain', '3500'); + }); + + it('Should display number of stops', () => { + cy.getByTestId('flight-stops').should('contain', '0'); + }); + }); + + describe('Flight Details Page - Timing Details', () => { + beforeEach(() => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + cy.getByTestId('schedule-search-result').first().click(); + cy.wait('@getFlightDetails'); + }); + + it('Should display departure gate', () => { + cy.getByTestId('flight-departure-gate').should('contain', '5'); + }); + + it('Should display departure terminal', () => { + cy.getByTestId('flight-departure-terminal').should('contain', 'A'); + }); + + it('Should display check-in time range', () => { + cy.getByTestId('flight-check-in-time').should('contain', '07:00'); + }); + + it('Should display boarding time', () => { + cy.getByTestId('flight-boarding-time').should('contain', '08:30'); + }); + + it('Should display arrival gate', () => { + cy.getByTestId('flight-arrival-gate').should('contain', '12'); + }); + + it('Should display arrival terminal', () => { + cy.getByTestId('flight-arrival-terminal').should('contain', 'B'); + }); + }); + + describe('Flight Details Page - Navigation', () => { + beforeEach(() => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + cy.getByTestId('schedule-search-result').first().click(); + cy.wait('@getFlightDetails'); + }); + + it('Should navigate to next flight', () => { + cy.getByTestId('next-flight-button').click(); + cy.wait('@getFlightDetails'); + cy.getByTestId('flight-details-number').should('be.visible'); + }); + + it('Should navigate to previous flight', () => { + cy.getByTestId('next-flight-button').click(); + cy.wait('@getFlightDetails'); + cy.getByTestId('prev-flight-button').click(); + cy.wait('@getFlightDetails'); + cy.getByTestId('flight-details-number').should('be.visible'); + }); + + it('Should return to search results', () => { + cy.getByTestId('back-to-search-button').click(); + cy.url().should('include', 'schedule'); + cy.getByTestId('schedule-search-results').should('be.visible'); + }); + + it('Should remember search filters when returning', () => { + cy.getByTestId('back-to-search-button').click(); + cy.getByTestId('schedule-departure-city-input').getByTestId('city-code').should('contain', 'MOW'); + cy.getByTestId('schedule-arrival-city-input').getByTestId('city-code').should('contain', 'LED'); + }); + + it('Should disable previous button on first flight', () => { + cy.getByTestId('prev-flight-button').should('be.disabled'); + }); + }); + + // ============================================================ + // FILTERS & SORTING TESTS (~15 tests) + // ============================================================ + + describe('Search Results - Filters', () => { + beforeEach(() => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + }); + + it('Should toggle time range filter', () => { + cy.getByTestId('time-filter-toggle').click(); + cy.getByTestId('time-filter-panel').should('be.visible'); + }); + + it('Should set minimum departure time', () => { + cy.getByTestId('time-filter-toggle').click(); + cy.getByTestId('time-filter-min-slider').invoke('val', '09').trigger('input'); + cy.getByTestId('schedule-search-result').each(($flight) => { + cy.wrap($flight).getByTestId('flight-departure-time').should('be.visible'); + }); + }); + + it('Should set maximum departure time', () => { + cy.getByTestId('time-filter-toggle').click(); + cy.getByTestId('time-filter-max-slider').invoke('val', '18').trigger('input'); + cy.getByTestId('schedule-search-result').each(($flight) => { + cy.wrap($flight).getByTestId('flight-departure-time').should('be.visible'); + }); + }); + + it('Should toggle airline filter', () => { + cy.getByTestId('airline-filter-toggle').click(); + cy.getByTestId('airline-filter-panel').should('be.visible'); + }); + + it('Should select single airline', () => { + cy.getByTestId('airline-filter-toggle').click(); + cy.getByTestId('airline-filter-option').first().click(); + cy.getByTestId('schedule-search-result').should('have.length.at.least', 1); + }); + + it('Should deselect airline', () => { + cy.getByTestId('airline-filter-toggle').click(); + cy.getByTestId('airline-filter-option').first().click(); + cy.getByTestId('airline-filter-option').first().click(); + cy.getByTestId('schedule-search-result').should('have.length.at.least', 1); + }); + + it('Should toggle price range filter', () => { + cy.getByTestId('price-filter-toggle').click(); + cy.getByTestId('price-filter-panel').should('be.visible'); + }); + + it('Should set minimum price', () => { + cy.getByTestId('price-filter-toggle').click(); + cy.getByTestId('price-filter-min-input').clear().type('3000'); + cy.getByTestId('schedule-search-result').should('have.length.at.least', 1); + }); + + it('Should set maximum price', () => { + cy.getByTestId('price-filter-toggle').click(); + cy.getByTestId('price-filter-max-input').clear().type('4000'); + cy.getByTestId('schedule-search-result').should('have.length.at.least', 1); + }); + + it('Should clear all filters', () => { + cy.getByTestId('clear-filters-button').click(); + cy.getByTestId('schedule-search-result').should('have.length.at.least', 1); + }); + }); + + describe('Search Results - Sorting', () => { + beforeEach(() => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + }); + + it('Should sort by departure time ascending', () => { + cy.getByTestId('sort-dropdown').click(); + cy.getByTestId('sort-option-departure-asc').click(); + cy.getByTestId('schedule-search-result').first().getByTestId('flight-departure-time').should('contain', '09:00'); + }); + + it('Should sort by departure time descending', () => { + cy.getByTestId('sort-dropdown').click(); + cy.getByTestId('sort-option-departure-desc').click(); + cy.getByTestId('schedule-search-result').first().getByTestId('flight-departure-time').should('contain', '21:00'); + }); + + it('Should sort by flight duration', () => { + cy.getByTestId('sort-dropdown').click(); + cy.getByTestId('sort-option-duration').click(); + cy.getByTestId('schedule-search-result').should('have.length.at.least', 1); + }); + + it('Should sort by price ascending', () => { + cy.getByTestId('sort-dropdown').click(); + cy.getByTestId('sort-option-price-asc').click(); + cy.getByTestId('schedule-search-result').first().getByTestId('flight-price').should('contain', '2800'); + }); + + it('Should sort by price descending', () => { + cy.getByTestId('sort-dropdown').click(); + cy.getByTestId('sort-option-price-desc').click(); + cy.getByTestId('schedule-search-result').first().getByTestId('flight-price').should('contain', '4200'); + }); + }); + + describe('Search Results - Result Display', () => { + beforeEach(() => { + cy.getByTestId('schedule-departure-city-input').type('Москва').type('{enter}'); + cy.wait(200); + cy.getByTestId('schedule-arrival-city-input').type('Санкт-Петербург').type('{enter}'); + cy.getByTestId('schedule-search-button').click(); + cy.wait('@getSchedule'); + }); + + it('Should display multiple flight results', () => { + cy.getByTestId('schedule-search-result').should('have.length', 5); + }); + + it('Should highlight flight on hover', () => { + cy.getByTestId('schedule-search-result').first().trigger('mouseover'); + cy.getByTestId('schedule-search-result').first().should('have.class', 'highlighted'); + }); + + it('Should show flight details on click', () => { + cy.getByTestId('schedule-search-result').first().click(); + cy.wait('@getFlightDetails'); + cy.getByTestId('flight-details-number').should('be.visible'); + }); + }); +});