const authenticatedUser = { id: 7, email: 'user@example.com', } function fillLoginForm(): void { cy.get('#login-email').type('user@example.com') cy.get('#login-password').type('correct-password') } describe('password login', () => { beforeEach(() => { cy.intercept('GET', '**/api/me', { statusCode: 401, body: { error: 'unauthenticated' }, }).as('me') }) it('logs in and redirects to the dashboard', () => { cy.intercept('POST', '**/api/login', (request) => { expect(request.body).to.deep.equal({ email: 'user@example.com', password: 'correct-password', }) request.reply({ statusCode: 200, body: { user: authenticatedUser }, }) }).as('login') cy.visit('/login') fillLoginForm() cy.get('form').submit() cy.wait('@login') cy.location('pathname').should('equal', '/dashboard') }) it('returns to a safe internal redirect after login', () => { cy.intercept('POST', '**/api/login', { statusCode: 200, body: { user: authenticatedUser }, }).as('login') cy.visit('/login?redirect=%2Fdashboard%3Ffocus%3Dtoday') fillLoginForm() cy.get('form').submit() cy.wait('@login') cy.location('pathname').should('equal', '/dashboard') cy.location('search').should('equal', '?focus=today') }) it('ignores an unsafe redirect after login', () => { cy.intercept('POST', '**/api/login', { statusCode: 200, body: { user: authenticatedUser }, }).as('login') cy.visit('/login?redirect=https%3A%2F%2Fexample.com') fillLoginForm() cy.get('form').submit() cy.wait('@login') cy.location('pathname').should('equal', '/dashboard') }) it('validates required login fields before submitting', () => { cy.visit('/login') cy.get('form').submit() cy.get('#login-email-error') .should('have.text', 'Enter a valid email address.') .and('be.visible') cy.get('#login-email').should('have.attr', 'aria-invalid', 'true') cy.get('#login-password-error') .should('have.text', 'Enter your password.') .and('be.visible') cy.get('#login-password').should('have.attr', 'aria-invalid', 'true') cy.location('pathname').should('equal', '/login') }) it('shows the backend invalid-credentials error', () => { cy.intercept('POST', '**/api/login', { statusCode: 401, body: { error: 'invalid credentials' }, }).as('login') cy.visit('/login') fillLoginForm() cy.get('form').submit() cy.wait('@login') cy.get('[role="alert"]') .should('have.text', 'invalid credentials') .and('be.visible') cy.location('pathname').should('equal', '/login') }) it('shows backend request errors', () => { cy.intercept('POST', '**/api/login', { statusCode: 400, body: { error: 'email is required' }, }).as('login') cy.visit('/login') fillLoginForm() cy.get('form').submit() cy.wait('@login') cy.get('[role="alert"]') .should('have.text', 'email is required') .and('be.visible') cy.get('#login-email-error').should('not.exist') }) it('shows a malformed-response error', () => { cy.intercept('POST', '**/api/login', { statusCode: 200, body: { user: { id: 7 } }, }).as('malformedLogin') cy.visit('/login') fillLoginForm() cy.get('form').submit() cy.wait('@malformedLogin') cy.get('[role="alert"]').should( 'have.text', 'Unable to log in. Please try again.', ) }) it('disables the form while login is pending', () => { cy.intercept('POST', '**/api/login', { delay: 500, statusCode: 200, body: { user: authenticatedUser }, }).as('login') cy.visit('/login') fillLoginForm() cy.get('form').submit() cy.get('button[type="submit"]') .should('be.disabled') .and('have.text', 'Logging in...') cy.get('#login-email').should('be.disabled') cy.get('#login-password').should('be.disabled') cy.wait('@login') }) })