- 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
187 lines
6 KiB
TypeScript
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')
|
|
})
|
|
})
|