Sprint: rerun jobs (#176)

* feat(PBI-jobs): voeg isDemo-prop door aan JobsBoard en JobDetailPane

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-jobs): voeg 'Opnieuw starten'-knop toe aan JobDetailPane

Toont een restart-knop voor jobs met status FAILED, CANCELLED of SKIPPED.
Gebruikt useTransition voor loading-state en DemoTooltip voor demo-modus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(PBI-jobs): voeg component-test toe voor JobDetailPane restart-knop

Test: knop zichtbaar voor FAILED, verborgen voor DONE, aanroep met juist id, disabled in demo-modus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(PBI-jobs): voeg F-14 restart-acceptatiecriteria toe aan functional.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-09 17:19:47 +02:00 committed by GitHub
parent 71319e629d
commit 6756450131
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 135 additions and 4 deletions

View file

@ -0,0 +1,75 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import type { JobWithRelations } from '@/actions/jobs-page'
vi.mock('@/actions/claude-jobs', () => ({
restartClaudeJobAction: vi.fn(),
}))
vi.mock('sonner', () => ({ toast: { error: vi.fn() } }))
import { restartClaudeJobAction } from '@/actions/claude-jobs'
import JobDetailPane from '@/components/jobs/job-detail-pane'
const mockAction = restartClaudeJobAction as ReturnType<typeof vi.fn>
function makeJob(status: JobWithRelations['status']): JobWithRelations {
return {
id: 'job-1',
kind: 'TASK_IMPLEMENTATION',
status,
taskCode: 'T-1',
taskTitle: 'Test taak',
ideaCode: null,
ideaTitle: null,
sprintGoal: null,
sprintCode: null,
productName: 'Scrum4Me',
modelId: null,
inputTokens: null,
outputTokens: null,
cacheReadTokens: null,
cacheWriteTokens: null,
costUsd: null,
branch: null,
prUrl: null,
error: null,
summary: null,
description: null,
verifyResult: null,
startedAt: null,
finishedAt: null,
createdAt: new Date('2026-01-01'),
sprintRunId: null,
}
}
beforeEach(() => {
vi.clearAllMocks()
mockAction.mockResolvedValue({ success: true })
})
describe('JobDetailPane restart button', () => {
it('toont de knop voor FAILED-jobs', () => {
render(<JobDetailPane job={makeJob('FAILED')} isDemo={false} />)
expect(screen.getByRole('button', { name: /opnieuw starten/i })).toBeInTheDocument()
})
it('toont de knop niet voor DONE-jobs', () => {
render(<JobDetailPane job={makeJob('DONE')} isDemo={false} />)
expect(screen.queryByRole('button', { name: /opnieuw starten/i })).not.toBeInTheDocument()
})
it('roept restartClaudeJobAction aan met het juiste id bij klik', () => {
render(<JobDetailPane job={makeJob('FAILED')} isDemo={false} />)
fireEvent.click(screen.getByRole('button', { name: /opnieuw starten/i }))
expect(mockAction).toHaveBeenCalledWith('job-1')
})
it('knop is disabled in demo-modus', () => {
render(<JobDetailPane job={makeJob('FAILED')} isDemo={true} />)
expect(screen.getByRole('button', { name: /opnieuw starten/i })).toBeDisabled()
})
})