Files
motovaultpro/frontend/cypress/e2e/stations.cy.ts
2025-11-04 18:46:46 -06:00

352 lines
11 KiB
TypeScript

/**
* @ai-summary End-to-end tests for Gas Stations feature
*
* Prerequisites:
* - Backend API running
* - Test user authenticated
* - Google Maps API key configured
*
* Run with: npm run e2e
*/
describe('Gas Stations Feature', () => {
beforeEach(() => {
// Login as test user
cy.login();
cy.visit('/stations');
});
describe('Search for Nearby Stations', () => {
it('should allow searching with current location', () => {
// Mock geolocation
cy.window().then((win) => {
cy.stub(win.navigator.geolocation, 'getCurrentPosition').callsFake((successCallback) => {
successCallback({
coords: {
latitude: 37.7749,
longitude: -122.4194,
accuracy: 10
}
} as GeolocationPosition);
});
});
// Click current location button
cy.contains('button', 'Current Location').click();
// Click search
cy.contains('button', 'Search').click();
// Verify results displayed
cy.get('[data-testid="station-card"]').should('have.length.greaterThan', 0);
cy.contains('Shell').or('Chevron').or('76').or('Exxon').should('be.visible');
});
it('should allow searching with manual coordinates', () => {
// Enter manual coordinates
cy.get('input[name="latitude"]').clear().type('37.7749');
cy.get('input[name="longitude"]').clear().type('-122.4194');
// Adjust radius
cy.get('[data-testid="radius-slider"]').click();
// Search
cy.contains('button', 'Search').click();
// Verify results
cy.get('[data-testid="station-card"]').should('exist');
});
it('should handle search errors gracefully', () => {
// Enter invalid coordinates
cy.get('input[name="latitude"]').clear().type('999');
cy.get('input[name="longitude"]').clear().type('999');
// Search
cy.contains('button', 'Search').click();
// Verify error message
cy.contains('error', { matchCase: false }).should('be.visible');
});
it('should display loading state during search', () => {
cy.intercept('POST', '/api/stations/search', {
delay: 1000,
body: { stations: [] }
});
cy.contains('button', 'Search').click();
// Verify loading indicator
cy.get('[data-testid="loading-skeleton"]').should('be.visible');
});
});
describe('View Stations on Map', () => {
beforeEach(() => {
// Perform a search first
cy.get('input[name="latitude"]').clear().type('37.7749');
cy.get('input[name="longitude"]').clear().type('-122.4194');
cy.contains('button', 'Search').click();
cy.wait(2000);
});
it('should display map with station markers', () => {
// Verify map is loaded
cy.get('[data-testid="station-map"]').should('be.visible');
// Verify markers present (if Google Maps loaded)
cy.get('.gm-style').should('exist');
});
it('should show info window when marker clicked', () => {
// This test assumes Google Maps is loaded
// Click first marker (may need custom data-testid on markers)
cy.get('[data-testid="map-marker"]').first().click();
// Verify info window content
cy.contains('Get Directions').should('be.visible');
});
it('should zoom to fit all markers', () => {
// Verify map auto-fits to show all markers
cy.get('[data-testid="station-map"]').should('be.visible');
// Check that multiple markers are in view
cy.get('[data-testid="station-card"]').then(($cards) => {
expect($cards.length).to.be.greaterThan(1);
});
});
});
describe('Save Station to Favorites', () => {
beforeEach(() => {
// Search first
cy.get('input[name="latitude"]').clear().type('37.7749');
cy.get('input[name="longitude"]').clear().type('-122.4194');
cy.contains('button', 'Search').click();
cy.wait(1000);
});
it('should save a station to favorites', () => {
// Find first station card
cy.get('[data-testid="station-card"]').first().within(() => {
// Click bookmark button
cy.get('button[title*="favorites"]').click();
});
// Verify optimistic UI update (bookmark filled)
cy.get('[data-testid="station-card"]').first().within(() => {
cy.get('button[title*="Remove"]').should('exist');
});
// Navigate to Saved tab
cy.contains('Saved Stations').click();
// Verify station appears in saved list
cy.get('[data-testid="saved-station-item"]').should('have.length.greaterThan', 0);
});
it('should allow adding nickname and notes', () => {
// Open station details/save modal (if exists)
cy.get('[data-testid="station-card"]').first().click();
// Enter nickname
cy.get('input[name="nickname"]').type('Work Gas Station');
cy.get('textarea[name="notes"]').type('Best prices in area');
// Save
cy.contains('button', 'Save').click();
// Verify saved
cy.contains('Work Gas Station').should('be.visible');
});
it('should prevent duplicate saves', () => {
// Save station
cy.get('[data-testid="station-card"]').first().within(() => {
cy.get('button[title*="favorites"]').click();
});
// Try to save again (should toggle off)
cy.get('[data-testid="station-card"]').first().within(() => {
cy.get('button[title*="Remove"]').click();
});
// Verify removed
cy.get('[data-testid="station-card"]').first().within(() => {
cy.get('button[title*="Add"]').should('exist');
});
});
});
describe('View Saved Stations List', () => {
beforeEach(() => {
// Navigate to saved tab
cy.contains('Saved Stations').click();
});
it('should display all saved stations', () => {
cy.get('[data-testid="saved-station-item"]').should('exist');
});
it('should show empty state when no saved stations', () => {
// If no stations saved
cy.get('body').then(($body) => {
if ($body.find('[data-testid="saved-station-item"]').length === 0) {
cy.contains('No saved stations').should('be.visible');
}
});
});
it('should display custom nicknames', () => {
// Verify stations show nicknames if set
cy.get('[data-testid="saved-station-item"]').first().should('contain.text', '');
});
});
describe('Delete Saved Station', () => {
beforeEach(() => {
// Ensure at least one station is saved
cy.visit('/stations');
cy.get('input[name="latitude"]').clear().type('37.7749');
cy.get('input[name="longitude"]').clear().type('-122.4194');
cy.contains('button', 'Search').click();
cy.wait(1000);
cy.get('[data-testid="station-card"]').first().within(() => {
cy.get('button[title*="favorites"]').click();
});
cy.wait(500);
cy.contains('Saved Stations').click();
});
it('should delete a saved station', () => {
// Count initial stations
cy.get('[data-testid="saved-station-item"]').its('length').then((initialCount) => {
// Delete first station
cy.get('[data-testid="saved-station-item"]').first().within(() => {
cy.get('button[title*="delete"]').or('button[title*="remove"]').click();
});
// Verify count decreased
cy.get('[data-testid="saved-station-item"]').should('have.length', initialCount - 1);
});
});
it('should show optimistic removal', () => {
// Get station name
cy.get('[data-testid="saved-station-item"]').first().invoke('text').then((stationName) => {
// Delete
cy.get('[data-testid="saved-station-item"]').first().within(() => {
cy.get('button[title*="delete"]').click();
});
// Verify immediately removed from UI
cy.contains(stationName).should('not.exist');
});
});
it('should handle delete errors', () => {
// Mock API error
cy.intercept('DELETE', '/api/stations/saved/*', {
statusCode: 500,
body: { error: 'Server error' }
});
// Try to delete
cy.get('[data-testid="saved-station-item"]').first().within(() => {
cy.get('button[title*="delete"]').click();
});
// Verify error message or rollback
cy.contains('error', { matchCase: false }).should('be.visible');
});
});
describe('Mobile Navigation Flow', () => {
beforeEach(() => {
cy.viewport('iphone-x');
cy.visit('/m/stations');
});
it('should navigate between tabs', () => {
// Verify Search tab active
cy.contains('Search').should('have.class', 'Mui-selected').or('have.attr', 'aria-selected', 'true');
// Click Saved tab
cy.contains('Saved').click();
cy.contains('Saved').should('have.class', 'Mui-selected');
// Click Map tab
cy.contains('Map').click();
cy.contains('Map').should('have.class', 'Mui-selected');
});
it('should display mobile-optimized layout', () => {
// Verify bottom navigation present
cy.get('[role="tablist"]').should('be.visible');
// Verify touch targets are 44px minimum
cy.get('button').first().should('have.css', 'min-height').and('match', /44/);
});
});
describe('Error Recovery', () => {
it('should recover from network errors', () => {
// Mock network failure
cy.intercept('POST', '/api/stations/search', {
forceNetworkError: true
});
// Try to search
cy.contains('button', 'Search').click();
// Verify error displayed
cy.contains('error', { matchCase: false }).or('network').should('be.visible');
// Retry button should be present
cy.contains('button', 'Retry').click();
});
it('should handle authentication errors', () => {
// Mock 401
cy.intercept('GET', '/api/stations/saved', {
statusCode: 401,
body: { error: 'Unauthorized' }
});
cy.visit('/stations');
// Should redirect to login or show auth error
cy.url().should('include', '/login').or('contain', '/auth');
});
});
describe('Integration with Fuel Logs', () => {
it('should allow selecting station when creating fuel log', () => {
// Navigate to fuel logs
cy.visit('/fuel-logs/new');
// Open station picker
cy.get('input[name="station"]').or('[data-testid="station-picker"]').click();
// Select a saved station
cy.contains('Work Station').or('Shell').click();
// Verify selection
cy.get('input[name="station"]').should('have.value', '');
});
});
});
/**
* Custom Cypress commands for stations feature
*/
declare global {
namespace Cypress {
interface Chainable {
login(): Chainable<void>;
}
}
}