352 lines
10 KiB
TypeScript
352 lines
10 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');
|
|
});
|
|
|
|
const enterSampleAddress = () => {
|
|
cy.get('input[name="street"]').clear().type('123 Main St');
|
|
cy.get('input[name="city"]').clear().type('San Francisco');
|
|
cy.get('select[name="state"]').select('CA');
|
|
cy.get('input[name="zip"]').clear().type('94105');
|
|
};
|
|
|
|
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 a manual address', () => {
|
|
// Enter manual address fields
|
|
enterSampleAddress();
|
|
|
|
// 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 require address details when location is unavailable', () => {
|
|
// Attempt to search without address or geolocation
|
|
cy.contains('button', 'Search').click();
|
|
|
|
// Verify error message
|
|
cy.contains('Enter Street, City, State, and ZIP', { 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
|
|
enterSampleAddress();
|
|
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
|
|
enterSampleAddress();
|
|
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>;
|
|
}
|
|
}
|
|
}
|