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([])
+ })
+})