hermes-kanban-miniapp/frontend/src/__tests__/App.test.tsx

77 lines
3 KiB
TypeScript
Raw Normal View History

2026-07-09 17:19:33 +03:00
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'
import App from '../App'
const tasksResponse = {
tasks: [
{ id: 't_1', title: 'Build thing', status: 'ready', assignee: 'developer', priority: 5, age: '1m' },
],
groups: {
triage: [],
todo: [],
ready: [{ id: 't_1', title: 'Build thing', status: 'ready', assignee: 'developer', priority: 5 }],
running: [],
blocked: [],
done: [],
archived: [],
},
}
beforeEach(() => {
vi.stubGlobal('fetch', vi.fn(async (url: string, init?: RequestInit) => {
if (String(url).includes('/api/tasks/t_1/events')) return new Response(JSON.stringify({ events: [{ kind: 'created' }] }))
if (String(url).includes('/api/tasks/t_1/runs')) return new Response(JSON.stringify({ runs: [{ id: 1, status: 'completed' }] }))
if (String(url).includes('/api/tasks/t_1/log')) return new Response(JSON.stringify({ log: 'tail', missing: false }))
if (String(url).endsWith('/api/tasks/t_1')) return new Response(JSON.stringify({ task: { ...tasksResponse.tasks[0], body: 'Full body' } }))
if (String(url).includes('/api/tasks') && init?.method === 'POST') return new Response(JSON.stringify({ task: { id: 't_new', title: 'Created', status: 'todo' } }))
if (String(url).includes('/api/tasks')) return new Response(JSON.stringify(tasksResponse))
return new Response(JSON.stringify({ status: 'ok' }))
}))
})
afterEach(() => {
cleanup()
vi.unstubAllGlobals()
})
describe('App', () => {
it('renders board columns', async () => {
render(<App />)
expect(await screen.findByText('ready')).toBeInTheDocument()
expect(screen.getByText('blocked')).toBeInTheDocument()
})
it('renders task cards with title status and assignee', async () => {
render(<App />)
expect(await screen.findByText('Build thing')).toBeInTheDocument()
expect(screen.getByText('developer')).toBeInTheDocument()
})
it('opens detail drawer and shows body events and log sections', async () => {
render(<App />)
fireEvent.click(await screen.findByText('Build thing'))
expect(await screen.findByText('Full body')).toBeInTheDocument()
expect(screen.getByText('Events')).toBeInTheDocument()
expect(screen.getByText('Log')).toBeInTheDocument()
})
it('create form validates title', async () => {
render(<App />)
fireEvent.click(await screen.findByText('Create task'))
fireEvent.click(screen.getByText('Create'))
expect(await screen.findByText('Title is required')).toBeInTheDocument()
})
it('shows API error state', async () => {
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({ detail: { message: 'boom' } }), { status: 500 })))
render(<App />)
expect(await screen.findByText(/boom/)).toBeInTheDocument()
})
it('does not crash when Telegram WebApp is absent', async () => {
vi.stubGlobal('Telegram', undefined)
render(<App />)
await waitFor(() => expect(screen.getByText('Hermes Kanban')).toBeInTheDocument())
})
})