118 lines
4.2 KiB
TypeScript
118 lines
4.2 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { UserMenu } from '../src/auth/UserMenu'
|
|
import { SessionCard } from '../src/auth/SessionCard'
|
|
import { login } from '../src/auth/useSession'
|
|
|
|
const anonymousBody = JSON.stringify({ authenticated: false, user: null })
|
|
const userBody = JSON.stringify({
|
|
authenticated: true,
|
|
user: {
|
|
sub: 'user-1',
|
|
name: 'Mats Test',
|
|
email: 'mats@example.com',
|
|
roles: ['admin'],
|
|
},
|
|
})
|
|
|
|
function stubFetch(body: string, ok = true): ReturnType<typeof vi.fn> {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok,
|
|
json: () => Promise.resolve(JSON.parse(body)),
|
|
})
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
return fetchMock
|
|
}
|
|
|
|
// jsdom's window.location is unforgeable; replace the whole global so the
|
|
// login() navigation target can be asserted.
|
|
beforeEach(() => {
|
|
vi.stubGlobal('location', { href: '', pathname: '/patterns', search: '?pattern=table' })
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('login()', () => {
|
|
it('navigates to the backend login with the current path as returnTo', () => {
|
|
login()
|
|
expect(window.location.href).toBe('/api/auth/login?returnTo=' + encodeURIComponent('/patterns?pattern=table'))
|
|
})
|
|
})
|
|
|
|
describe('UserMenu', () => {
|
|
it('shows Sign in when anonymous', async () => {
|
|
stubFetch(anonymousBody)
|
|
render(<UserMenu />)
|
|
await waitFor(() => expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument())
|
|
})
|
|
|
|
it('shows the avatar and a sign-out popover when authenticated', async () => {
|
|
stubFetch(userBody)
|
|
render(<UserMenu />)
|
|
|
|
const avatar = await waitFor(() => screen.getByRole('button', { name: 'Mats Test' }))
|
|
expect(avatar).toHaveTextContent('MT')
|
|
|
|
fireEvent.click(avatar)
|
|
expect(screen.getByRole('menu')).toBeInTheDocument()
|
|
expect(screen.getByText('mats@example.com')).toBeInTheDocument()
|
|
|
|
const fetchMock = vi.mocked(globalThis.fetch)
|
|
fireEvent.click(screen.getByRole('menuitem', { name: /sign out/i }))
|
|
await waitFor(() =>
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/auth/logout', { method: 'POST' }),
|
|
)
|
|
// Back to anonymous.
|
|
await waitFor(() => expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument())
|
|
})
|
|
|
|
it('treats an unreachable backend as anonymous', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('backend down')))
|
|
render(<UserMenu />)
|
|
await waitFor(() => expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument())
|
|
})
|
|
|
|
it('closes the popover on Escape', async () => {
|
|
stubFetch(userBody)
|
|
render(<UserMenu />)
|
|
fireEvent.click(await waitFor(() => screen.getByRole('button', { name: 'Mats Test' })))
|
|
expect(screen.getByRole('menu')).toBeInTheDocument()
|
|
fireEvent.keyDown(document, { key: 'Escape' })
|
|
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('SessionCard', () => {
|
|
it('offers sign-in with a note when anonymous', async () => {
|
|
stubFetch(anonymousBody)
|
|
render(<SessionCard />)
|
|
const button = await waitFor(() => screen.getByRole('button', { name: 'Sign in' }))
|
|
expect(screen.getByText(/not signed in/i)).toBeInTheDocument()
|
|
|
|
fireEvent.click(button)
|
|
expect(window.location.href).toBe('/api/auth/login?returnTo=' + encodeURIComponent('/'))
|
|
})
|
|
|
|
it('shows the signed-in user, roles, and sub when authenticated', async () => {
|
|
stubFetch(userBody)
|
|
render(<SessionCard />)
|
|
expect(await screen.findByText('Mats Test')).toBeInTheDocument()
|
|
expect(screen.getByText('mats@example.com')).toBeInTheDocument()
|
|
expect(screen.getByText('sub: user-1')).toBeInTheDocument()
|
|
expect(screen.getByText('admin')).toBeInTheDocument()
|
|
})
|
|
|
|
it('signs out and flips back to anonymous', async () => {
|
|
stubFetch(userBody)
|
|
render(<SessionCard />)
|
|
fireEvent.click(await screen.findByRole('button', { name: 'Sign out' }))
|
|
const fetchMock = vi.mocked(globalThis.fetch)
|
|
await waitFor(() =>
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/auth/logout', { method: 'POST' }),
|
|
)
|
|
await waitFor(() => expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument())
|
|
})
|
|
})
|