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