this repo has no description
1import { describe, it, expect, beforeEach } from 'vitest' 2import { render, screen, fireEvent, waitFor } from '@testing-library/svelte' 3import Login from '../routes/Login.svelte' 4import { 5 setupFetchMock, 6 mockEndpoint, 7 jsonResponse, 8 errorResponse, 9 mockData, 10 clearMocks, 11} from './mocks' 12describe('Login', () => { 13 beforeEach(() => { 14 clearMocks() 15 setupFetchMock() 16 window.location.hash = '' 17 }) 18 describe('initial render', () => { 19 it('renders login form with all elements and correct initial state', () => { 20 render(Login) 21 expect(screen.getByRole('heading', { name: /sign in/i })).toBeInTheDocument() 22 expect(screen.getByLabelText(/handle or email/i)).toBeInTheDocument() 23 expect(screen.getByLabelText(/password/i)).toBeInTheDocument() 24 expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument() 25 expect(screen.getByRole('button', { name: /sign in/i })).toBeDisabled() 26 expect(screen.getByText(/don't have an account/i)).toBeInTheDocument() 27 expect(screen.getByRole('link', { name: /create one/i })).toHaveAttribute('href', '#/register') 28 }) 29 }) 30 describe('form validation', () => { 31 it('enables submit button only when both fields are filled', async () => { 32 render(Login) 33 const identifierInput = screen.getByLabelText(/handle or email/i) 34 const passwordInput = screen.getByLabelText(/password/i) 35 const submitButton = screen.getByRole('button', { name: /sign in/i }) 36 await fireEvent.input(identifierInput, { target: { value: 'testuser' } }) 37 expect(submitButton).toBeDisabled() 38 await fireEvent.input(identifierInput, { target: { value: '' } }) 39 await fireEvent.input(passwordInput, { target: { value: 'password123' } }) 40 expect(submitButton).toBeDisabled() 41 await fireEvent.input(identifierInput, { target: { value: 'testuser' } }) 42 expect(submitButton).not.toBeDisabled() 43 }) 44 }) 45 describe('login submission', () => { 46 it('calls createSession with correct credentials', async () => { 47 let capturedBody: Record<string, string> | null = null 48 mockEndpoint('com.atproto.server.createSession', (_url, options) => { 49 capturedBody = JSON.parse((options?.body as string) || '{}') 50 return jsonResponse(mockData.session()) 51 }) 52 render(Login) 53 await fireEvent.input(screen.getByLabelText(/handle or email/i), { target: { value: 'testuser@example.com' } }) 54 await fireEvent.input(screen.getByLabelText(/password/i), { target: { value: 'mypassword' } }) 55 await fireEvent.click(screen.getByRole('button', { name: /sign in/i })) 56 await waitFor(() => { 57 expect(capturedBody).toEqual({ 58 identifier: 'testuser@example.com', 59 password: 'mypassword', 60 }) 61 }) 62 }) 63 it('shows styled error message on invalid credentials', async () => { 64 mockEndpoint('com.atproto.server.createSession', () => 65 errorResponse('AuthenticationRequired', 'Invalid identifier or password', 401) 66 ) 67 render(Login) 68 await fireEvent.input(screen.getByLabelText(/handle or email/i), { target: { value: 'wronguser' } }) 69 await fireEvent.input(screen.getByLabelText(/password/i), { target: { value: 'wrongpassword' } }) 70 await fireEvent.click(screen.getByRole('button', { name: /sign in/i })) 71 await waitFor(() => { 72 const errorDiv = screen.getByText(/invalid identifier or password/i) 73 expect(errorDiv).toBeInTheDocument() 74 expect(errorDiv).toHaveClass('error') 75 }) 76 }) 77 it('navigates to dashboard on successful login', async () => { 78 mockEndpoint('com.atproto.server.createSession', () => 79 jsonResponse(mockData.session()) 80 ) 81 render(Login) 82 await fireEvent.input(screen.getByLabelText(/handle or email/i), { target: { value: 'test' } }) 83 await fireEvent.input(screen.getByLabelText(/password/i), { target: { value: 'password' } }) 84 await fireEvent.click(screen.getByRole('button', { name: /sign in/i })) 85 await waitFor(() => { 86 expect(window.location.hash).toBe('#/dashboard') 87 }) 88 }) 89 }) 90 describe('account verification flow', () => { 91 it('shows verification form with all controls when account is not verified', async () => { 92 mockEndpoint('com.atproto.server.createSession', () => ({ 93 ok: false, 94 status: 401, 95 json: async () => ({ 96 error: 'AccountNotVerified', 97 message: 'Account not verified', 98 did: 'did:web:test.tranquil.dev:u:testuser', 99 }), 100 })) 101 render(Login) 102 await fireEvent.input(screen.getByLabelText(/handle or email/i), { target: { value: 'unverified@test.com' } }) 103 await fireEvent.input(screen.getByLabelText(/password/i), { target: { value: 'password' } }) 104 await fireEvent.click(screen.getByRole('button', { name: /sign in/i })) 105 await waitFor(() => { 106 expect(screen.getByRole('heading', { name: /verify your account/i })).toBeInTheDocument() 107 expect(screen.getByLabelText(/verification code/i)).toBeInTheDocument() 108 expect(screen.getByRole('button', { name: /resend code/i })).toBeInTheDocument() 109 expect(screen.getByRole('button', { name: /back to login/i })).toBeInTheDocument() 110 }) 111 }) 112 it('returns to login form when clicking back', async () => { 113 mockEndpoint('com.atproto.server.createSession', () => ({ 114 ok: false, 115 status: 401, 116 json: async () => ({ 117 error: 'AccountNotVerified', 118 message: 'Account not verified', 119 did: 'did:web:test.tranquil.dev:u:testuser', 120 }), 121 })) 122 render(Login) 123 await fireEvent.input(screen.getByLabelText(/handle or email/i), { target: { value: 'test' } }) 124 await fireEvent.input(screen.getByLabelText(/password/i), { target: { value: 'password' } }) 125 await fireEvent.click(screen.getByRole('button', { name: /sign in/i })) 126 await waitFor(() => { 127 expect(screen.getByRole('button', { name: /back to login/i })).toBeInTheDocument() 128 }) 129 await fireEvent.click(screen.getByRole('button', { name: /back to login/i })) 130 await waitFor(() => { 131 expect(screen.getByRole('heading', { name: /sign in/i })).toBeInTheDocument() 132 expect(screen.queryByLabelText(/verification code/i)).not.toBeInTheDocument() 133 }) 134 }) 135 }) 136})