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
This commit is contained in:
Joakim Mörling 2026-05-16 16:11:58 +02:00
parent 851cd8afa0
commit 2506a0283c
13 changed files with 165 additions and 88 deletions

View file

@ -42,35 +42,35 @@ test.describe('Admin dashboard', () => {
test('shows seeded order data', async ({ page }) => {
await page.goto('/admin')
await expect(page.locator('.admin-dashboard__plate').first()).toBeVisible()
await expect(page.locator('.admin__plate').first()).toBeVisible()
await expect(page.getByText('DEF456').first()).toBeVisible()
await expect(page.getByText('GHI789').first()).toBeVisible()
})
test('click row expands letter content', async ({ page }) => {
test('click expand button shows letter content', async ({ page }) => {
await page.goto('/admin')
const rows = page.locator('.admin-dashboard__row')
await rows.first().click()
const expandBtns = page.locator('.admin__expand-btn')
await expandBtns.first().click()
await expect(page.getByText('Brevtext')).toBeVisible()
})
test('click expanded row collapses it', async ({ page }) => {
test('click expand button again collapses it', async ({ page }) => {
await page.goto('/admin')
const rows = page.locator('.admin-dashboard__row')
await rows.first().click()
const expandBtns = page.locator('.admin__expand-btn')
await expandBtns.first().click()
await expect(page.getByText('Brevtext')).toBeVisible()
await rows.first().click()
await expandBtns.first().click()
await expect(page.getByText('Brevtext')).not.toBeVisible()
})
test('status dropdown changes update order status', async ({ page }) => {
await page.goto('/admin')
const selects = page.locator('.admin-dashboard__status-select')
const selects = page.locator('.admin__status-select')
await selects.first().selectOption('delivered')
const updatedSelect = selects.first()
@ -87,21 +87,21 @@ test.describe('Admin dashboard', () => {
test('expanded row shows tracking input and save button', async ({ page }) => {
await page.goto('/admin')
const rows = page.locator('.admin-dashboard__row')
await rows.first().click()
const expandBtns = page.locator('.admin__expand-btn')
await expandBtns.first().click()
await expect(page.getByText('Spårnings-ID')).toBeVisible()
await expect(page.locator('.admin-dashboard__tracking-input')).toBeVisible()
await expect(page.getByRole('button', { name: 'Spara spårning' })).toBeVisible()
await expect(page.getByText('Spårnings-ID').first()).toBeVisible()
await expect(page.locator('.admin__tracking-input')).toBeVisible()
await expect(page.getByRole('button', { name: 'Spara' })).toBeVisible()
})
test('shows PostNord link when trackingId exists', async ({ page }) => {
await page.goto('/admin')
const rows = page.locator('.admin-dashboard__row')
await rows.last().click()
const expandBtns = page.locator('.admin__expand-btn')
await expandBtns.last().click()
const trackingLink = page.locator('.admin-dashboard__tracking-link')
const trackingLink = page.locator('.admin__tracking-link')
await expect(trackingLink).toBeVisible()
await expect(trackingLink).toHaveAttribute('href', /postnord/)
})
@ -109,10 +109,11 @@ test.describe('Admin dashboard', () => {
test('hides PostNord link when trackingId is null', async ({ page }) => {
await page.goto('/admin')
const defRow = page.locator('.admin-dashboard__row', { hasText: 'DEF456' }).first()
await defRow.click()
const defRow = page.locator('.admin__row', { hasText: 'DEF456' }).first()
const expandBtn = defRow.locator('.admin__expand-btn')
await expandBtn.click()
const trackingLink = page.locator('.admin-dashboard__tracking-link')
const trackingLink = page.locator('.admin__tracking-link')
await expect(trackingLink).not.toBeVisible()
})
})

View file

@ -44,7 +44,7 @@ test.describe('Compose flow', () => {
await page.goto('/compose?plate=ABC123')
const button = page.getByRole('button', { name: 'Skicka brev (49 kr)' })
const button = page.getByRole('button', { name: 'Fortsätt till betalning' })
await expect(button).toBeDisabled()
})
@ -58,7 +58,7 @@ test.describe('Compose flow', () => {
await page.goto('/compose?plate=ABC123')
await page.getByLabel('Ditt meddelande').fill('Hej fin bil!')
const button = page.getByRole('button', { name: 'Skicka brev (49 kr)' })
const button = page.getByRole('button', { name: 'Fortsätt till betalning' })
await expect(button).toBeEnabled()
await button.click()
@ -79,7 +79,7 @@ test.describe('Compose flow', () => {
await page.getByLabel('Ditt meddelande').fill('Testmeddelande')
await expect(
page.getByText('Detta brev skickades via BilHej.se'),
page.getByText('Detta brev skickades via Bilhej'),
).toBeVisible()
await expect(page.getByText('Transportstyrelsens fordonsregister')).toBeVisible()
})

View file

@ -98,6 +98,51 @@ test.describe('Header auth state', () => {
).not.toBeVisible()
await expect(header.getByText('test@bilhalsning.se')).not.toBeVisible()
})
test('logout redirects to home page', async ({ page }) => {
const jwt = makeJwt({ sub: 'test@bilhalsning.se', role: 'user' })
await page.goto('/orders')
await page.evaluate(
(token) => localStorage.setItem('auth_token', token),
jwt,
)
await page.goto('/orders')
await page.waitForURL('/orders')
await page.locator('header').getByRole('button', { name: 'Logga ut' }).click()
await expect(page).toHaveURL('/')
})
test('shows admin link when admin is authenticated', async ({ page }) => {
const jwt = makeJwt({ sub: 'admin@bilhalsning.se', role: 'admin' })
await page.goto('/')
await page.evaluate(
(token) => localStorage.setItem('auth_token', token),
jwt,
)
await page.goto('/')
const header = page.locator('header')
const adminLink = header.getByRole('link', { name: 'Admin' })
await expect(adminLink).toBeVisible()
await expect(adminLink).toHaveAttribute('href', '/admin')
})
test('does not show admin link for regular user', async ({ page }) => {
const jwt = makeJwt({ sub: 'test@bilhalsning.se', role: 'user' })
await page.goto('/')
await page.evaluate(
(token) => localStorage.setItem('auth_token', token),
jwt,
)
await page.goto('/')
const header = page.locator('header')
await expect(
header.getByRole('link', { name: 'Admin' }),
).not.toBeVisible()
})
})
function makeJwt(payload: Record<string, unknown>): string {

View file

@ -45,4 +45,21 @@ test.describe('Login page', () => {
'password',
)
})
test('login form has name attributes and form action', async ({ page }) => {
await page.goto('/logga-in')
const form = page.locator('form')
await expect(form).toHaveAttribute('method', 'post')
await expect(form).toHaveAttribute('action', '/api/auth/login')
await expect(page.getByLabel('E-postadress')).toHaveAttribute(
'name',
'email',
)
await expect(page.getByLabel('Lösenord')).toHaveAttribute(
'name',
'password',
)
})
})

View file

@ -12,7 +12,7 @@ test.describe('Payment redirect', () => {
test('can navigate to payment page from compose', async ({ page }) => {
await page.goto('/compose?plate=ABC123')
await page.getByLabel('Ditt meddelande').fill('Hej fin bil!')
await page.getByRole('button', { name: 'Skicka brev (49 kr)' }).click()
await page.getByRole('button', { name: 'Fortsätt till betalning' }).click()
await expect(page).toHaveURL(/\/betalning\//)
await expect(page.getByRole('heading', { name: 'Betalning' })).toBeVisible()
@ -20,15 +20,15 @@ test.describe('Payment redirect', () => {
await expect(page.getByText('ABC123')).toBeVisible()
})
test('Betalt button marks order as paid and redirects to orders', async ({
test('payment button marks order as paid and redirects to orders', async ({
page,
}) => {
await page.goto('/compose?plate=DEF456')
await page.getByLabel('Ditt meddelande').fill('Vill köpa din bil.')
await page.getByRole('button', { name: 'Skicka brev (49 kr)' }).click()
await page.getByRole('button', { name: 'Fortsätt till betalning' }).click()
await page.waitForURL(/\/betalning\//)
await page.getByRole('button', { name: 'Betalt' }).click()
await page.getByRole('button', { name: 'Genomför testbetalning' }).click()
await expect(page).toHaveURL('/orders')
await expect(page.getByText('DEF456').first()).toBeVisible()
@ -44,9 +44,9 @@ test.describe('Payment redirect', () => {
test('shows mock payment note', async ({ page }) => {
await page.goto('/compose?plate=GHI789')
await page.getByLabel('Ditt meddelande').fill('Hej!')
await page.getByRole('button', { name: 'Skicka brev (49 kr)' }).click()
await page.getByRole('button', { name: 'Fortsätt till betalning' }).click()
await page.waitForURL(/\/betalning\//)
await expect(page.getByText(/mock-betalning/i)).toBeVisible()
await expect(page.locator('.payment__note')).toBeVisible()
})
})

View file

@ -5,6 +5,6 @@ import AboutPage from '@/pages/AboutPage.vue'
describe('AboutPage', () => {
it('renders heading', () => {
const wrapper = mount(AboutPage)
expect(wrapper.text()).toContain('Om BilHälsning')
expect(wrapper.text()).toContain('Om Bilhej')
})
})

View file

@ -66,13 +66,10 @@ describe('AdminDashboard', () => {
)
})
it('renders heading and subtitle', async () => {
it('renders heading', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Administration')
expect(wrapper.text()).toContain(
'Hantera beställningar, mallar och användare',
)
})
it('shows loading state initially', async () => {
@ -124,30 +121,31 @@ describe('AdminDashboard', () => {
expect(wrapper.text()).toContain('Kunde inte hämta beställningar')
})
it('expands row on click to show letter content', async () => {
it('expands row on button click to show letter content', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
const rows = wrapper.findAll('.admin__row')
expect(rows.length).toBe(2)
await rows[0].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[0].trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Hej fin bil!')
expect(wrapper.text()).toContain('Brevtext')
})
it('collapses row on second click', async () => {
it('collapses row on second button click', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[0].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[0].trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Hej fin bil!')
await rows[0].trigger('click')
await expandBtns[0].trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).not.toContain('Hej fin bil!')
})
@ -156,12 +154,12 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[0].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[0].trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Hej fin bil!')
await rows[1].trigger('click')
await expandBtns[1].trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).not.toContain('Hej fin bil!')
expect(wrapper.text()).toContain('Vill köpa din bil.')
@ -171,7 +169,7 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const selects = wrapper.findAll('.admin-dashboard__status-select')
const selects = wrapper.findAll('.admin__status-select')
expect(selects.length).toBe(2)
})
@ -185,7 +183,7 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const selects = wrapper.findAll('.admin-dashboard__status-select')
const selects = wrapper.findAll('.admin__status-select')
await selects[0].trigger('change')
await new Promise((r) => setTimeout(r, 50))
@ -208,7 +206,7 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const selects = wrapper.findAll('.admin-dashboard__status-select')
const selects = wrapper.findAll('.admin__status-select')
await selects[0].trigger('change')
await new Promise((r) => setTimeout(r, 50))
@ -226,24 +224,24 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[0].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[0].trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.find('.admin-dashboard__tracking').exists()).toBe(true)
expect(wrapper.find('.admin-dashboard__tracking-input').exists()).toBe(true)
expect(wrapper.find('.admin-dashboard__tracking-save').exists()).toBe(true)
expect(wrapper.find('.admin__tracking-row').exists()).toBe(true)
expect(wrapper.find('.admin__tracking-input').exists()).toBe(true)
expect(wrapper.find('.btn--primary').exists()).toBe(true)
})
it('shows tracking link when trackingId is set', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[0].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[0].trigger('click')
await new Promise((r) => setTimeout(r, 50))
const link = wrapper.find('.admin-dashboard__tracking-link')
const link = wrapper.find('.admin__tracking-link')
expect(link.exists()).toBe(true)
expect(link.attributes('href')).toContain('postnord')
expect(link.attributes('target')).toBe('_blank')
@ -253,11 +251,11 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[1].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[1].trigger('click')
await new Promise((r) => setTimeout(r, 50))
const link = wrapper.find('.admin-dashboard__tracking-link')
const link = wrapper.find('.admin__tracking-link')
expect(link.exists()).toBe(false)
})
@ -269,11 +267,11 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[1].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[1].trigger('click')
await new Promise((r) => setTimeout(r, 50))
await wrapper.find('.admin-dashboard__tracking-save').trigger('click')
await wrapper.find('.btn--primary').trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(globalThis.fetch).toHaveBeenCalledWith(
@ -294,11 +292,11 @@ describe('AdminDashboard', () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const rows = wrapper.findAll('.admin-dashboard__row')
await rows[1].trigger('click')
const expandBtns = wrapper.findAll('.admin__expand-btn')
await expandBtns[1].trigger('click')
await new Promise((r) => setTimeout(r, 50))
await wrapper.find('.admin-dashboard__tracking-save').trigger('click')
await wrapper.find('.btn--primary').trigger('click')
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Kunde inte spara spårnings-ID')

View file

@ -90,7 +90,7 @@ describe('ComposePage', () => {
const { wrapper } = await mountPage()
const textarea = wrapper.find('textarea')
await textarea.setValue('a'.repeat(901))
const counter = wrapper.find('.compose__counter')
const counter = wrapper.find('.field__hint')
expect(counter.classes()).toContain('compose__counter--warn')
})
@ -174,7 +174,7 @@ describe('ComposePage', () => {
it('shows GDPR footer in preview', async () => {
const { wrapper } = await mountPage()
expect(wrapper.text()).toContain('Detta brev skickades via BilHej.se')
expect(wrapper.text()).toContain('Detta brev skickades via Bilhej')
})
it('shows Visa mallar button', async () => {

View file

@ -21,16 +21,16 @@ function mountHome(router: ReturnType<typeof createTestRouter>) {
}
describe('HomePage', () => {
it('renders subtitle', () => {
it('renders headline', () => {
const router = createTestRouter()
const wrapper = mountHome(router)
expect(wrapper.text()).toContain('Skicka ett brev till en fordonsägare')
expect(wrapper.text()).toContain('Skicka ett brev')
})
it('does not show CTA button initially', () => {
const router = createTestRouter()
const wrapper = mountHome(router)
expect(wrapper.find('.home__cta').exists()).toBe(false)
expect(wrapper.find('.btn--primary').exists()).toBe(false)
})
it('does not show CTA while loading', async () => {
@ -41,7 +41,7 @@ describe('HomePage', () => {
await plateInput.vm.$emit('lookup', 'ABC123')
await wrapper.vm.$nextTick()
expect(wrapper.find('.home__cta').exists()).toBe(false)
expect(wrapper.find('.btn--primary').exists()).toBe(false)
})
it('does not show CTA after not-found', async () => {
@ -52,7 +52,7 @@ describe('HomePage', () => {
await plateInput.vm.$emit('lookup', 'UNKNOWN')
await new Promise((resolve) => setTimeout(resolve, 500))
expect(wrapper.find('.home__cta').exists()).toBe(false)
expect(wrapper.find('.btn--primary').exists()).toBe(false)
})
it('shows CTA button when vehicle data present', async () => {
@ -64,9 +64,9 @@ describe('HomePage', () => {
await plateInput.vm.$emit('lookup', 'ABC123')
await new Promise((resolve) => setTimeout(resolve, 500))
const cta = wrapper.find('.home__cta')
const cta = wrapper.find('.btn--primary')
expect(cta.exists()).toBe(true)
expect(cta.text()).toBe('Skicka ett brev till ägaren')
expect(cta.text()).toBe('Fortsätt till brevet')
})
it('CTA links to compose page with plate query param', async () => {
@ -78,7 +78,7 @@ describe('HomePage', () => {
await plateInput.vm.$emit('lookup', 'ABC123')
await new Promise((resolve) => setTimeout(resolve, 500))
const cta = wrapper.find('.home__cta')
const cta = wrapper.find('.btn--primary')
const href = cta.attributes('href')
expect(href).toBe('/compose?plate=ABC123')
})

View file

@ -70,6 +70,23 @@ describe('LoginPage', () => {
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"]')

View file

@ -136,7 +136,7 @@ describe('OrdersPage', () => {
vi.mocked(globalThis.fetch).mockResolvedValue(mockFetchResponse(200, []))
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
expect(wrapper.text()).toContain('Du har inga beställningar ännu')
expect(wrapper.text()).toContain('Inga beställningar ännu')
})
it('shows error state on API failure', async () => {
@ -151,8 +151,8 @@ describe('OrdersPage', () => {
it('applies correct badge class for status', async () => {
const { wrapper } = mountPage()
await new Promise((r) => setTimeout(r, 50))
const badges = wrapper.findAll('.orders__badge')
expect(badges[0].classes()).toContain('badge--green')
expect(badges[1].classes()).toContain('badge--gray')
const badges = wrapper.findAll('.badge')
expect(badges[0].classes()).toContain('badge--success')
expect(badges[1].classes()).toContain('badge--muted')
})
})

View file

@ -66,16 +66,16 @@ describe('PaymentRedirect', () => {
expect(wrapper.text()).toContain('ABC123')
})
it('shows Betalt button', async () => {
it('shows payment button', async () => {
const { wrapper } = await mountPage()
const button = wrapper.find('.payment__button')
const button = wrapper.find('.btn--primary')
expect(button.exists()).toBe(true)
expect(button.text()).toBe('Betalt')
expect(button.text()).toBe('Genomför testbetalning')
})
it('shows mock payment note', async () => {
it('shows test payment note', async () => {
const { wrapper } = await mountPage()
expect(wrapper.text()).toContain('mock-betalning')
expect(wrapper.text()).toContain('testbetalning')
})
it('calls payOrder on button click', async () => {
@ -89,7 +89,7 @@ describe('PaymentRedirect', () => {
})
const { wrapper } = await mountPage()
await wrapper.find('.payment__button').trigger('click')
await wrapper.find('.btn--primary').trigger('click')
expect(mockPayOrder).toHaveBeenCalledWith('order-1')
})
@ -105,7 +105,7 @@ describe('PaymentRedirect', () => {
})
const { wrapper, router } = await mountPage()
await wrapper.find('.payment__button').trigger('click')
await wrapper.find('.btn--primary').trigger('click')
await vi.waitFor(() => {
expect(router.currentRoute.value.name).toBe('orders')
@ -116,7 +116,7 @@ describe('PaymentRedirect', () => {
mockPayOrder.mockRejectedValue(new Error('Network error'))
const { wrapper } = await mountPage()
await wrapper.find('.payment__button').trigger('click')
await wrapper.find('.btn--primary').trigger('click')
await vi.waitFor(() => {
expect(wrapper.text()).toContain('Kunde inte genomföra betalningen')
@ -127,7 +127,7 @@ describe('PaymentRedirect', () => {
mockPayOrder.mockImplementation(() => new Promise(() => {}))
const { wrapper } = await mountPage()
const button = wrapper.find('.payment__button')
const button = wrapper.find('.btn--primary')
await button.trigger('click')
expect(button.attributes('disabled')).toBeDefined()

View file

@ -24,7 +24,6 @@ describe('TemplatePicker', () => {
expect(wrapper.emitted('select')).toHaveLength(1)
expect(wrapper.emitted('select')![0][0]).toMatchObject({
name: 'Komplimang',
icon: '🌟',
})
})