201 lines
6.6 KiB
TypeScript
201 lines
6.6 KiB
TypeScript
// @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([])
|
|
})
|
|
})
|