bilhej/frontend/src/__tests__/LoginPage.spec.ts
Joakim Mörling 2506a0283c test: update Vitest and E2E specs for redesigned UI
- Update HomePage specs: new headline, CTA class from btn--success to btn--primary
- Update ComposePage specs: new button text, brand name in GDPR footer
- Update PaymentRedirect specs: button text, class, and test payment note
- Update TemplatePicker specs: remove emoji icon assertion
- Update AdminDashboard specs: expand button selectors instead of row clicks
- Update AppHeader specs: BilHälsning to Bilhej brand text
- Update AboutPage specs: BilHälsning to Bilhej heading
- Update App specs: new homepage headline text
- Update OrdersPage specs: badge class renames
- Update LoginPage specs: form name/action attribute tests
- Update E2E compose specs: button text, GDPR footer brand name
- Update E2E payment specs: button text and note selectors
- Update E2E admin-dashboard specs: expand button and tracking label selectors
- Update E2E header-auth specs: new test additions for admin visibility
2026-05-16 16:11:58 +02:00

187 lines
6 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createMemoryHistory } from 'vue-router'
import { createPinia } from 'pinia'
import LoginPage from '@/pages/LoginPage.vue'
function mockFetchResponse(status: number, body: unknown) {
return Promise.resolve({
ok: status >= 200 && status < 300,
status,
json: () => Promise.resolve(body),
})
}
function createTestRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/logga-in', name: 'login', component: LoginPage },
{ path: '/', name: 'home', component: { template: '<div>Home</div>' } },
{
path: '/registrera',
name: 'register',
component: { template: '<div>Register</div>' },
},
{
path: '/compose',
name: 'compose',
component: { template: '<div>Compose</div>' },
},
],
})
}
function mountPage() {
const router = createTestRouter()
const pinia = createPinia()
router.push('/logga-in')
return {
router,
wrapper: mount(LoginPage, {
global: { plugins: [router, pinia] },
}),
}
}
describe('LoginPage', () => {
beforeEach(() => {
localStorage.clear()
globalThis.fetch = vi.fn()
vi.mocked(globalThis.fetch).mockResolvedValue(
mockFetchResponse(200, { token: 'test-token' }),
)
})
it('renders heading and subtitle', async () => {
const { wrapper } = mountPage()
expect(wrapper.text()).toContain('Logga in')
expect(wrapper.text()).toContain('Ange din e-postadress och ditt lösenord')
})
it('renders email and password fields', async () => {
const { wrapper } = mountPage()
expect(wrapper.find('#email').exists()).toBe(true)
expect(wrapper.find('#password').exists()).toBe(true)
})
it('does not render confirm password field', async () => {
const { wrapper } = mountPage()
expect(wrapper.find('#confirm-password').exists()).toBe(false)
})
it('form element has method post and action', async () => {
const { wrapper } = mountPage()
const form = wrapper.find('form')
expect(form.attributes('method')).toBe('post')
expect(form.attributes('action')).toBe('/api/auth/login')
})
it('email input has name attribute', async () => {
const { wrapper } = mountPage()
expect(wrapper.find('#email').attributes('name')).toBe('email')
})
it('password input has name attribute', async () => {
const { wrapper } = mountPage()
expect(wrapper.find('#password').attributes('name')).toBe('password')
})
it('disables submit when fields are empty', async () => {
const { wrapper } = mountPage()
const button = wrapper.find('button[type="submit"]')
expect(button.attributes('disabled')).toBeDefined()
})
it('enables submit when both fields have values', async () => {
const { wrapper } = mountPage()
await wrapper.find('#email').setValue('test@example.com')
await wrapper.find('#password').setValue('password123')
const button = wrapper.find('button[type="submit"]')
expect(button.attributes('disabled')).toBeUndefined()
})
it('shows loading text while submitting', async () => {
globalThis.fetch = vi.fn().mockImplementation(() => new Promise(() => {}))
const { wrapper } = mountPage()
await wrapper.find('#email').setValue('test@example.com')
await wrapper.find('#password').setValue('password123')
await wrapper.find('form').trigger('submit.prevent')
expect(wrapper.text()).toContain('Loggar in...')
})
it('calls login API and redirects to home on success', async () => {
const { wrapper, router } = mountPage()
await wrapper.find('#email').setValue('test@example.com')
await wrapper.find('#password').setValue('password123')
await wrapper.find('form').trigger('submit.prevent')
await new Promise((resolve) => setTimeout(resolve, 50))
expect(globalThis.fetch).toHaveBeenCalledWith(
'/api/auth/login',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
email: 'test@example.com',
password: 'password123',
}),
}),
)
expect(router.currentRoute.value.name).toBe('home')
})
it('shows generic error on login failure', async () => {
vi.mocked(globalThis.fetch).mockResolvedValue(
mockFetchResponse(401, { message: 'Felaktig e-post eller lösenord' }),
)
const { wrapper } = mountPage()
await wrapper.find('#email').setValue('test@example.com')
await wrapper.find('#password').setValue('wrongpassword')
await wrapper.find('form').trigger('submit.prevent')
await new Promise((resolve) => setTimeout(resolve, 50))
expect(wrapper.text()).toContain('Felaktig e-post eller lösenord')
})
it('does not leak specific error messages', async () => {
vi.mocked(globalThis.fetch).mockResolvedValue(
mockFetchResponse(500, { message: 'Internal server error' }),
)
const { wrapper } = mountPage()
await wrapper.find('#email').setValue('test@example.com')
await wrapper.find('#password').setValue('password123')
await wrapper.find('form').trigger('submit.prevent')
await new Promise((resolve) => setTimeout(resolve, 50))
expect(wrapper.text()).toContain('Felaktig e-post eller lösenord')
expect(wrapper.text()).not.toContain('Internal server error')
})
it('renders register link', async () => {
const { wrapper } = mountPage()
expect(wrapper.text()).toContain('Har du inget konto?')
})
it('redirects to query param after login', async () => {
const router = createTestRouter()
await router.push({ path: '/logga-in', query: { redirect: '/compose' } })
const pinia = createPinia()
const wrapper = mount(LoginPage, {
global: { plugins: [router, pinia] },
})
await wrapper.find('#email').setValue('test@example.com')
await wrapper.find('#password').setValue('password123')
await wrapper.find('form').trigger('submit.prevent')
await new Promise((resolve) => setTimeout(resolve, 50))
expect(router.currentRoute.value.fullPath).toBe('/compose')
})
})