From 80a7d793b6cdde7fa37f20e4aac0fe6f934d3eff Mon Sep 17 00:00:00 2001
From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com>
Date: Sun, 3 May 2026 13:38:41 +0200
Subject: [PATCH] feat(solo): BatchEnqueueBlockerDialog component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Nieuw dialoogvenster dat gebruiker waarschuwt bij gedetecteerde blocker: toont blockerReason in NL, prefixCount taken vóór blokkade, confirm-knop (disabled met tooltip bij count=0) en annuleer-knop. 7 tests voor rendering, click-handlers en disabled-state.
---
.../batch-enqueue-blocker-dialog.test.tsx | 114 ++++++++++++++++++
.../solo/batch-enqueue-blocker-dialog.tsx | 87 +++++++++++++
2 files changed, 201 insertions(+)
create mode 100644 __tests__/components/solo/batch-enqueue-blocker-dialog.test.tsx
create mode 100644 components/solo/batch-enqueue-blocker-dialog.tsx
diff --git a/__tests__/components/solo/batch-enqueue-blocker-dialog.test.tsx b/__tests__/components/solo/batch-enqueue-blocker-dialog.test.tsx
new file mode 100644
index 0000000..e349227
--- /dev/null
+++ b/__tests__/components/solo/batch-enqueue-blocker-dialog.test.tsx
@@ -0,0 +1,114 @@
+// @vitest-environment jsdom
+import '@testing-library/jest-dom'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { render, screen, fireEvent } from '@testing-library/react'
+
+vi.mock('@/components/ui/dialog', () => ({
+ Dialog: ({ open, children }: { open: boolean; onOpenChange?: (v: boolean) => void; children: React.ReactNode }) =>
+ open ?
{children}
: null,
+ DialogContent: ({ children }: { children: React.ReactNode }) => {children}
,
+ DialogHeader: ({ children }: { children: React.ReactNode }) => {children}
,
+ DialogTitle: ({ children }: { children: React.ReactNode }) => {children}
,
+}))
+vi.mock('@/components/ui/button', () => ({
+ Button: ({
+ children,
+ onClick,
+ disabled,
+ variant,
+ }: {
+ children?: React.ReactNode
+ onClick?: () => void
+ disabled?: boolean
+ variant?: string
+ }) => (
+
+ ),
+}))
+vi.mock('@/components/ui/tooltip', () => ({
+ TooltipProvider: ({ children }: { children: React.ReactNode }) => <>{children}>,
+ Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}>,
+ TooltipTrigger: ({ render: r, children }: { render?: React.ReactElement; children?: React.ReactNode }) =>
+ r ? <>{r}> : <>{children}>,
+ TooltipContent: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}))
+
+import { BatchEnqueueBlockerDialog } from '@/components/solo/batch-enqueue-blocker-dialog'
+
+const DEFAULT_PROPS = {
+ open: true,
+ onOpenChange: vi.fn(),
+ prefixCount: 3,
+ blockerReason: 'task-review' as const,
+ blockerLabel: 'Story X — Task Y (in review)',
+ onConfirm: vi.fn(),
+ onCancel: vi.fn(),
+}
+
+beforeEach(() => {
+ vi.clearAllMocks()
+})
+
+describe('BatchEnqueueBlockerDialog', () => {
+ it('renders title and blocker info for task-review', () => {
+ render()
+
+ expect(screen.getByRole('heading')).toHaveTextContent('Blokkade gedetecteerd')
+ expect(screen.getByText(/Een taak staat op 'review'/)).toBeInTheDocument()
+ expect(screen.getByText(/Story X — Task Y/)).toBeInTheDocument()
+ })
+
+ it('renders correct blocker label for pbi-blocked', () => {
+ render(
+
+ )
+
+ expect(screen.getByText(/De PBI is geblokkeerd/)).toBeInTheDocument()
+ expect(screen.getByText(/PBI Z/)).toBeInTheDocument()
+ })
+
+ it('calls onConfirm when primary button is clicked', () => {
+ render()
+
+ fireEvent.click(screen.getByText(/Stuur 3 taken tot aan blokkade/))
+
+ expect(DEFAULT_PROPS.onConfirm).toHaveBeenCalledTimes(1)
+ })
+
+ it('calls onCancel when cancel button is clicked', () => {
+ render()
+
+ fireEvent.click(screen.getByText('Annuleer'))
+
+ expect(DEFAULT_PROPS.onCancel).toHaveBeenCalledTimes(1)
+ })
+
+ it('disables confirm button and shows tooltip when prefixCount is 0', () => {
+ render()
+
+ const confirmBtn = screen.getByText(/Stuur 0/).closest('button')
+ expect(confirmBtn).toBeDisabled()
+ expect(screen.getByTestId('tooltip-content')).toHaveTextContent('Geen taken vóór blokkade')
+ })
+
+ it('does not render when open is false', () => {
+ render()
+
+ expect(screen.queryByTestId('dialog')).not.toBeInTheDocument()
+ })
+
+ it('uses singular taak when prefixCount is 1', () => {
+ render()
+
+ expect(screen.getByText(/Stuur 1 taak tot aan blokkade/)).toBeInTheDocument()
+ expect(screen.getByText(/1 taak vóór de blokkade/)).toBeInTheDocument()
+ })
+})
diff --git a/components/solo/batch-enqueue-blocker-dialog.tsx b/components/solo/batch-enqueue-blocker-dialog.tsx
new file mode 100644
index 0000000..81bf593
--- /dev/null
+++ b/components/solo/batch-enqueue-blocker-dialog.tsx
@@ -0,0 +1,87 @@
+'use client'
+
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
+
+interface BatchEnqueueBlockerDialogProps {
+ open: boolean
+ onOpenChange: (v: boolean) => void
+ prefixCount: number
+ blockerReason: 'task-review' | 'pbi-blocked'
+ blockerLabel: string
+ onConfirm: () => void
+ onCancel: () => void
+}
+
+const BLOCKER_REASON_LABELS: Record = {
+ 'task-review': "Een taak staat op 'review'",
+ 'pbi-blocked': 'De PBI is geblokkeerd',
+}
+
+export function BatchEnqueueBlockerDialog({
+ open,
+ onOpenChange,
+ prefixCount,
+ blockerReason,
+ blockerLabel,
+ onConfirm,
+ onCancel,
+}: BatchEnqueueBlockerDialogProps) {
+ const noTasksBeforeBlocker = prefixCount === 0
+
+ return (
+
+ )
+}