test(ideas): voeg componenttests toe voor IdeasFilterPopover en persistentie

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scrum4Me Agent 2026-05-15 04:14:35 +02:00
parent e398b84930
commit 3f40bc2f0f

View file

@ -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: () => <div data-testid="idea-row-actions" />,
}))
// --- 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
}) => (
<PopoverCtx.Provider value={{ open: open ?? false, onOpenChange: onOpenChange ?? (() => {}) }}>
{children}
</PopoverCtx.Provider>
),
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 ? <div data-testid="popover-content">{children}</div> : 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> = {}): 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(<IdeaList ideas={IDEAS} products={[]} isDemo={false} />)
// 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(<IdeaList ideas={IDEAS} products={[]} isDemo={false} />)
// 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(<IdeaList ideas={IDEAS} products={[]} isDemo={false} />)
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(<IdeaList ideas={IDEAS} products={[]} isDemo={false} />)
// 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(<IdeaList ideas={IDEAS} products={[]} isDemo={false} />)
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(<IdeaList ideas={IDEAS} products={[]} isDemo={false} />)
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([])
})
})