diff --git a/ClientApp/cypress/integration/features/popular-requests.cy.ts b/ClientApp/cypress/integration/features/popular-requests.cy.ts new file mode 100644 index 00000000..c025bec2 --- /dev/null +++ b/ClientApp/cypress/integration/features/popular-requests.cy.ts @@ -0,0 +1,245 @@ +import { POPULAR_REQUESTS } from '../../support/fixtures'; + +describe('Popular Requests Widget', () => { + beforeEach(() => { + cy.intercept('GET', '**/api/popular-requests/**', { statusCode: 200, body: POPULAR_REQUESTS }).as('getPopularRequests'); + cy.forbidGeolocation(); + cy.visit('/'); + }); + + describe('Widget Load Tests', () => { + it('Should render widget on initial page load', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-requests-widget').should('exist'); + }); + + it('Should be visible in viewport', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-requests-widget').should('be.visible'); + }); + + it('Should have correct styling and layout', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-requests-widget').should('have.css', 'display').and('not.equal', 'none'); + }); + + it('Should have correct container dimensions', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-requests-widget').then(($widget) => { + expect($widget.width()).to.be.greaterThan(0); + expect($widget.height()).to.be.greaterThan(0); + }); + }); + + it('Should display widget title/header correctly', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-requests-widget').within(() => { + cy.getByTestId('popular-requests-title').should('exist').and('be.visible'); + }); + }); + }); + + describe('Display Tests', () => { + it('Should display all popular request items from API', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').should('have.length', POPULAR_REQUESTS.length); + }); + + it('Should display departure city in each item', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').each(($item, index) => { + cy.wrap($item).within(() => { + cy.getByTestId('popular-request-departure').should('contain', POPULAR_REQUESTS[index].departure); + }); + }); + }); + + it('Should display arrival city in each item', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').each(($item, index) => { + cy.wrap($item).within(() => { + cy.getByTestId('popular-request-arrival').should('contain', POPULAR_REQUESTS[index].arrival); + }); + }); + }); + + it('Should display flight count/frequency in each item', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').each(($item, index) => { + cy.wrap($item).within(() => { + cy.getByTestId('popular-request-frequency').should('exist').and('be.visible'); + }); + }); + }); + + it('Should have clickable items', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().should('have.css', 'cursor').and('not.equal', 'default'); + }); + + it('Should display items with proper styling (colors, spacing)', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().then(($item) => { + const styles = window.getComputedStyle($item[0]); + expect(styles.padding).to.not.be.empty; + expect(styles.margin).to.not.be.empty; + }); + }); + + it('Should render all items with correct data from first request', () => { + cy.wait('@getPopularRequests'); + const firstItem = POPULAR_REQUESTS[0]; + cy.getByTestId('popular-request-item').first().within(() => { + cy.getByTestId('popular-request-departure').should('contain', firstItem.departure); + cy.getByTestId('popular-request-arrival').should('contain', firstItem.arrival); + cy.getByTestId('popular-request-departure-code').should('contain', firstItem.departureCode); + cy.getByTestId('popular-request-arrival-code').should('contain', firstItem.arrivalCode); + }); + }); + + it('Should render all items with correct data from second request', () => { + cy.wait('@getPopularRequests'); + const secondItem = POPULAR_REQUESTS[1]; + cy.getByTestId('popular-request-item').eq(1).within(() => { + cy.getByTestId('popular-request-departure').should('contain', secondItem.departure); + cy.getByTestId('popular-request-arrival').should('contain', secondItem.arrival); + cy.getByTestId('popular-request-departure-code').should('contain', secondItem.departureCode); + cy.getByTestId('popular-request-arrival-code').should('contain', secondItem.arrivalCode); + }); + }); + + it('Should render all items with correct data from third request', () => { + cy.wait('@getPopularRequests'); + const thirdItem = POPULAR_REQUESTS[2]; + cy.getByTestId('popular-request-item').eq(2).within(() => { + cy.getByTestId('popular-request-departure').should('contain', thirdItem.departure); + cy.getByTestId('popular-request-arrival').should('contain', thirdItem.arrival); + cy.getByTestId('popular-request-departure-code').should('contain', thirdItem.departureCode); + cy.getByTestId('popular-request-arrival-code').should('contain', thirdItem.arrivalCode); + }); + }); + + it('Should display frequency/high indicator for first item', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().within(() => { + cy.getByTestId('popular-request-frequency').should('contain', POPULAR_REQUESTS[0].frequency); + }); + }); + }); + + describe('Navigation Tests', () => { + it('Should navigate to search page when clicking item', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('include', '/onlineboard/'); + }); + + it('Should include departure city code in URL after click', () => { + cy.wait('@getPopularRequests'); + const firstItem = POPULAR_REQUESTS[0]; + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('include', firstItem.departureCode); + }); + + it('Should include arrival city code in URL after click', () => { + cy.wait('@getPopularRequests'); + const firstItem = POPULAR_REQUESTS[0]; + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('include', firstItem.arrivalCode); + }); + + it('Should navigate with different parameters for different items', () => { + cy.wait('@getPopularRequests'); + const firstItem = POPULAR_REQUESTS[0]; + const secondItem = POPULAR_REQUESTS[1]; + + cy.getByTestId('popular-request-item').first().click(); + cy.url().then((firstUrl) => { + cy.visit('/'); + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').eq(1).click(); + cy.url().then((secondUrl) => { + expect(firstUrl).to.not.equal(secondUrl); + }); + }); + }); + + it('Should navigate to departure city page', () => { + cy.wait('@getPopularRequests'); + const firstItem = POPULAR_REQUESTS[0]; + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('include', 'departure'); + }); + + it('Should navigate to correct date range', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('match', /\d{8}-\d{4}-\d{4}/); + }); + + it('Should preserve language on navigation', () => { + cy.visit('/en-us/'); + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('include', '/en-us/'); + }); + + it('Should make search page load correctly after navigation', () => { + cy.wait('@getPopularRequests'); + cy.getByTestId('popular-request-item').first().click(); + cy.getByTestId('board-search-result', { timeout: 10000 }).should('exist'); + }); + }); + + describe('API Fallback Tests', () => { + it('Should fall back to fixture data when API fails', () => { + // Intercept API to fail, but first reset and visit + cy.intercept('GET', '**/api/popular-requests/**', { statusCode: 500 }).as('failedRequest'); + cy.visit('/'); + cy.wait('@failedRequest'); + + // Widget should still be visible with fallback data + cy.getByTestId('popular-requests-widget').should('be.visible'); + }); + + it('Should display fallback data correctly on API error', () => { + cy.intercept('GET', '**/api/popular-requests/**', { statusCode: 500 }).as('failedRequest'); + cy.visit('/'); + cy.wait('@failedRequest'); + + // Fallback data should still have items + cy.getByTestId('popular-request-item').should('have.length.greaterThan', 0); + }); + + it('Should allow navigation even with API fallback', () => { + cy.intercept('GET', '**/api/popular-requests/**', { statusCode: 500 }).as('failedRequest'); + cy.visit('/'); + cy.wait('@failedRequest'); + + cy.getByTestId('popular-request-item').first().click(); + cy.url().should('include', '/onlineboard/'); + }); + + it('Should handle network timeout gracefully', () => { + cy.intercept('GET', '**/api/popular-requests/**', (req) => { + req.destroy(); + }).as('timedOutRequest'); + cy.visit('/'); + cy.wait('@timedOutRequest'); + + cy.getByTestId('popular-requests-widget').should('be.visible'); + cy.getByTestId('popular-request-item').should('have.length.greaterThan', 0); + }); + + it('Should render widget without breaking layout on API error', () => { + cy.intercept('GET', '**/api/popular-requests/**', { statusCode: 500 }).as('failedRequest'); + cy.visit('/'); + cy.wait('@failedRequest'); + + cy.getByTestId('popular-requests-widget').then(($widget) => { + expect($widget.width()).to.be.greaterThan(0); + expect($widget.height()).to.be.greaterThan(0); + }); + }); + }); +});