From 3f40bc2f0f20f43a9955a55df642bab44e23b8a4 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Fri, 15 May 2026 04:14:35 +0200 Subject: [PATCH] test(ideas): voeg componenttests toe voor IdeasFilterPopover en persistentie Co-Authored-By: Claude Sonnet 4.6 --- __tests__/components/ideas/idea-list.test.tsx | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 __tests__/components/ideas/idea-list.test.tsx diff --git a/__tests__/components/ideas/idea-list.test.tsx b/__tests__/components/ideas/idea-list.test.tsx new file mode 100644 index 0000000..a2b2d93 --- /dev/null +++ b/__tests__/components/ideas/idea-list.test.tsx @@ -0,0 +1,201 @@ +// @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 React from 'react' + +// --- Navigation mock --- +vi.mock('next/navigation', () => ({ + useRouter: () => ({ push: vi.fn(), refresh: vi.fn() }), +})) + +// --- Actions mocks --- +vi.mock('@/actions/ideas', () => ({ + createIdeaAction: vi.fn(), + archiveIdeaAction: vi.fn(), +})) + +vi.mock('@/actions/user-settings', () => ({ + updateUserSettingsAction: vi.fn().mockResolvedValue({ success: true, settings: {} }), +})) + +// --- Sonner mock --- +vi.mock('sonner', () => ({ + toast: { error: vi.fn(), success: vi.fn() }, +})) + +// --- IdeaRowActions mock (complex component with many deps) --- +vi.mock('@/components/ideas/idea-row-actions', () => ({ + IdeaRowActions: () =>
, +})) + +// --- DemoTooltip mock --- +vi.mock('@/components/shared/demo-tooltip', () => ({ + DemoTooltip: ({ children }: { children: React.ReactNode }) => <>{children}, +})) + +// --- Popover mock — controlled via open prop --- +vi.mock('@/components/ui/popover', () => { + const PopoverCtx = React.createContext<{ + open: boolean + onOpenChange: (v: boolean) => void + }>({ open: false, onOpenChange: () => {} }) + + return { + Popover: ({ + children, + open, + onOpenChange, + }: { + children: React.ReactNode + open?: boolean + onOpenChange?: (v: boolean) => void + }) => ( + {}) }}> + {children} + + ), + PopoverTrigger: ({ render: renderEl }: { render: React.ReactElement<{ onClick?: (e: React.MouseEvent) => void }> }) => { + const { open, onOpenChange } = React.useContext(PopoverCtx) + return React.cloneElement(renderEl, { + onClick: (e: React.MouseEvent) => { + onOpenChange(!open) + renderEl.props.onClick?.(e) + }, + }) + }, + PopoverContent: ({ children }: { children: React.ReactNode }) => { + const { open } = React.useContext(PopoverCtx) + return open ?
{children}
: null + }, + } +}) + +// Import after mocks +import { useUserSettingsStore } from '@/stores/user-settings/store' +import { IdeaList } from '@/components/ideas/idea-list' +import type { IdeaDto } from '@/lib/idea-dto' + +// Minimal IdeaDto factory +function makeIdea(overrides: Partial = {}): IdeaDto { + return { + id: 'idea-1', + code: 'ID-1', + title: 'Test Idee', + description: null, + status: 'draft', + product_id: null, + product: null, + pbi_id: null, + pbi: null, + secondary_products: [], + archived: false, + has_grill_md: false, + has_plan_md: false, + created_at: '2024-01-01T00:00:00.000Z', + updated_at: '2024-01-01T00:00:00.000Z', + ...overrides, + } +} + +const IDEAS: IdeaDto[] = [ + makeIdea({ id: 'idea-1', code: 'ID-1', title: 'Idee Concept', status: 'draft' }), + makeIdea({ id: 'idea-2', code: 'ID-2', title: 'Idee Gegrilld', status: 'grilled' }), + makeIdea({ id: 'idea-3', code: 'ID-3', title: 'Idee Gepland', status: 'planned' }), +] + +beforeEach(() => { + vi.clearAllMocks() + useUserSettingsStore.getState().hydrate({}, false) +}) + +describe('IdeaList — filterpopover', () => { + it('toont de "Filters"-knop in de toolbar (geen inline chip-rij)', () => { + render() + + // Filters-knop aanwezig + expect(screen.getByText('Filters')).toBeInTheDocument() + + // Status-labels zoals "Concept" mogen NIET los zichtbaar zijn zonder popover te openen + // (anders was de oude inline chip-rij er nog) + expect(screen.queryByRole('button', { name: 'Concept' })).not.toBeInTheDocument() + }) + + it('klik op "Filters" opent de popover en toont 11 statusopties', () => { + render() + + // Popover nog niet open: content niet zichtbaar + expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() + + fireEvent.click(screen.getByText('Filters')) + + // Content verschijnt + expect(screen.getByTestId('popover-content')).toBeInTheDocument() + + // 11 statusopties + "Alle" = 12 buttons in de popover + // Controleer specifiek de 11 status-labels + const statusLabels = [ + 'Concept', 'Grillen', 'Gegrilld', 'Plannen', 'Plan klaar', + 'Plan beoordelen', 'Gepland', 'Grill mislukt', 'Plan mislukt', + 'Beoordeling mislukt', 'Plan beoordeeld', + ] + for (const label of statusLabels) { + expect(screen.getByRole('button', { name: label })).toBeInTheDocument() + } + }) + + it('klik op een statuschip schrijft de status naar de store', () => { + render() + + fireEvent.click(screen.getByText('Filters')) + fireEvent.click(screen.getByRole('button', { name: 'Concept' })) + + const stored = + useUserSettingsStore.getState().entities.settings.views?.ideasList?.filterStatuses + expect(stored).toContain('draft') + }) + + it('gehydrateerde filter toont "Filters (1)" en filtert de tabel', () => { + useUserSettingsStore + .getState() + .hydrate({ views: { ideasList: { filterStatuses: ['draft'] } } }, false) + + render() + + // Trigger toont het actieve filteraantal + expect(screen.getByText('Filters (1)')).toBeInTheDocument() + + // Alleen het concept-idee is zichtbaar; de andere twee worden weggefilterd + expect(screen.getByText('Idee Concept')).toBeInTheDocument() + expect(screen.queryByText('Idee Gegrilld')).not.toBeInTheDocument() + expect(screen.queryByText('Idee Gepland')).not.toBeInTheDocument() + }) + + it('"Wis filters" is disabled wanneer geen filter actief is', () => { + render() + + fireEvent.click(screen.getByText('Filters')) + + const wisButton = screen.getByRole('button', { name: 'Wis filters' }) + expect(wisButton).toBeDisabled() + }) + + it('"Wis filters" is enabled en wist de filter wanneer een filter actief is', () => { + useUserSettingsStore + .getState() + .hydrate({ views: { ideasList: { filterStatuses: ['draft'] } } }, false) + + render() + + fireEvent.click(screen.getByText('Filters (1)')) + + const wisButton = screen.getByRole('button', { name: 'Wis filters' }) + expect(wisButton).not.toBeDisabled() + + fireEvent.click(wisButton) + + const stored = + useUserSettingsStore.getState().entities.settings.views?.ideasList?.filterStatuses + expect(stored).toEqual([]) + }) +})