bilhej/frontend/e2e/header-auth.spec.ts
Joakim Mörling 7a95c1423c
Some checks failed
CI / Lint, type check, unit tests, coverage (pull_request) Successful in 2m22s
CI / E2E browser tests (pull_request) Failing after 1m3s
Make customer-facing UI usable on smartphones.
Mobile traffic was breaking on narrow viewports because the header nav
overflowed and several pages used desktop-only spacing. This adds a
shared phone breakpoint, a hamburger menu, and scroll-to-top on route
changes so footer and menu navigation always land at the top of the page.

- Add --page-gutter and max-width 639px rules in base.css
- AppHeader: hamburger panel on small screens; flat account links on mobile
- AppFooter: stack footer links vertically on phones
- Home, compose, edit order, orders, auth, and legal pages: tighter gutters
  and responsive layout (orders card actions stack; home grids single-column)
- Router scrollBehavior: scroll to top on navigation; restore on browser back
- Tests: AppHeader menu toggle, Router scrollBehavior, mobile Playwright checks

Admin page is intentionally unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 13:03:35 +02:00

249 lines
7.6 KiB
TypeScript

import { test, expect } from '@playwright/test'
test.describe('Header auth state', () => {
test('shows login and register links when not authenticated', async ({
page,
}) => {
await page.goto('/')
const header = page.locator('header')
await expect(header.getByRole('link', { name: 'Logga in' })).toBeVisible()
await expect(
header.getByRole('link', { name: 'Registrera' }),
).toBeVisible()
})
test('does not show logout button when not authenticated', async ({
page,
}) => {
await page.goto('/')
const header = page.locator('header')
await expect(
header.getByRole('button', { name: 'Logga ut' }),
).not.toBeVisible()
})
test('shows email and logout when authenticated', async ({ page }) => {
const jwt = makeJwt({ sub: 'test@bilhej.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.getByText('test@bilhej.se')).toBeVisible()
await expect(
header.getByRole('button', { name: 'Logga ut' }),
).toBeVisible()
})
test('shows orders link when authenticated', async ({ page }) => {
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
await page.goto('/')
await page.evaluate(
(token) => localStorage.setItem('auth_token', token),
jwt,
)
await page.goto('/')
const header = page.locator('header')
const ordersLink = header.getByRole('link', {
name: 'Mina beställningar',
})
await expect(ordersLink).toBeVisible()
await expect(ordersLink).toHaveAttribute('href', '/orders')
})
test('hides login and register links when authenticated', async ({
page,
}) => {
const jwt = makeJwt({ sub: 'test@bilhej.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: 'Logga in' }),
).not.toBeVisible()
await expect(
header.getByRole('link', { name: 'Registrera' }),
).not.toBeVisible()
})
test('logout restores login and register links', async ({ page }) => {
const jwt = makeJwt({ sub: 'test@bilhej.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 header.getByRole('button', { name: 'Logga ut' }).click()
await expect(
header.getByRole('link', { name: 'Logga in' }),
).toBeVisible()
await expect(
header.getByRole('link', { name: 'Registrera' }),
).toBeVisible()
await expect(
header.getByRole('button', { name: 'Logga ut' }),
).not.toBeVisible()
await expect(header.getByText('test@bilhej.se')).not.toBeVisible()
})
test('logout redirects to home page', async ({ page }) => {
const jwt = makeJwt({ sub: 'test@bilhej.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@bilhej.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()
})
test('shows settings button when authenticated', async ({ page }) => {
await authenticateUser(page)
const header = page.locator('header')
await expect(
header.getByRole('button', { name: 'Inställningar' }),
).toBeVisible()
})
test('settings menu links to change email and password pages', async ({
page,
}) => {
await authenticateUser(page)
const header = page.locator('header')
const settingsButton = header.getByRole('button', { name: 'Inställningar' })
await settingsButton.click()
const menu = header.getByRole('menu')
await expect(
menu.getByRole('menuitem', { name: 'Byt e-postadress' }),
).toHaveAttribute('href', '/andra-epost')
await expect(
menu.getByRole('menuitem', { name: 'Byt lösenord' }),
).toHaveAttribute('href', '/andra-losenord')
})
test('highlights settings button on change password page', async ({
page,
}) => {
await authenticateUser(page)
await page.goto('/andra-losenord')
const settingsButton = page
.locator('header')
.getByRole('button', { name: 'Inställningar' })
await expect(settingsButton).toHaveClass(/app-header__settings-trigger--active/)
await expect(
page.getByRole('heading', { name: 'Byt lösenord' }),
).toBeVisible()
})
test('highlights settings button on change email page', async ({ page }) => {
await authenticateUser(page)
await page.goto('/andra-epost')
const settingsButton = page
.locator('header')
.getByRole('button', { name: 'Inställningar' })
await expect(settingsButton).toHaveClass(/app-header__settings-trigger--active/)
await expect(
page.getByRole('heading', { name: 'Byt e-postadress' }),
).toBeVisible()
})
})
test.describe('Header on mobile viewport', () => {
test.use({ viewport: { width: 390, height: 844 } })
test('menu reveals navigation links when authenticated', async ({ page }) => {
await authenticateUser(page)
await page.goto('/')
const header = page.locator('header')
await expect(
header.getByRole('link', { name: 'Mina beställningar' }),
).not.toBeVisible()
await header.getByRole('button', { name: 'Öppna meny' }).click()
await expect(
header.getByRole('link', { name: 'Mina beställningar' }),
).toBeVisible()
await expect(
header.getByRole('link', { name: 'Byt e-postadress' }),
).toBeVisible()
})
test('home page has no horizontal overflow', async ({ page }) => {
await page.goto('/')
const scrollWidth = await page.evaluate(
() => document.documentElement.scrollWidth,
)
const clientWidth = await page.evaluate(
() => document.documentElement.clientWidth,
)
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1)
})
})
async function authenticateUser(page: import('@playwright/test').Page) {
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
await page.goto('/')
await page.evaluate((token) => localStorage.setItem('auth_token', token), jwt)
await page.goto('/')
}
function makeJwt(payload: Record<string, unknown>): string {
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
const body = btoa(JSON.stringify(payload))
const signature = 'test-sig'
return `${header}.${body}.${signature}`
}