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 ( + + + + Blokkade gedetecteerd + + +
+

+ {BLOCKER_REASON_LABELS[blockerReason]}:{' '} + {blockerLabel}. +

+ {noTasksBeforeBlocker ? ( +

Er zijn geen taken vóór de blokkade om in te plannen.

+ ) : ( +

+ {prefixCount === 1 + ? `Er is ${prefixCount} taak vóór de blokkade.` + : `Er zijn ${prefixCount} taken vóór de blokkade.`} +

+ )} +
+ +
+ + + + + + + } + /> + {noTasksBeforeBlocker && ( + + Geen taken vóór blokkade + + )} + + +
+
+
+ ) +}