From 5874975ff94e4a731a83e3d7e51ee53833500a48 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 27 Aug 2026 08:26:27 +0200 Subject: [PATCH] =?UTF-8?q?chore(test):=205=20Geister-Tests=20entfernt=20(?= =?UTF-8?q?Komponenten=20wurden=20bereits=20in=20db4701b=20als=20BUG-080/0?= =?UTF-8?q?82=20unused=20gel=C3=B6scht)=20und=20Playwright-e2e-Specs=20aus?= =?UTF-8?q?=20der=20Vitest-Einsammelung=20ausgeschlossen=20=E2=80=94=20sie?= =?UTF-8?q?=20geh=C3=B6ren=20zum=20eigenen=20Runner=20mit=20eigener=20Konf?= =?UTF-8?q?iguration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/__tests__/PWAInstallPrompt.test.tsx | 96 ------------------- .../contacts/ContactEditModal.test.tsx | 84 ---------------- .../src/__tests__/tags/BulkTagDialog.test.tsx | 86 ----------------- .../src/__tests__/tags/TagPicker.test.tsx | 89 ----------------- .../tags/TagPicker.validation.test.tsx | 66 ------------- frontend/vite.config.ts | 1 + 6 files changed, 1 insertion(+), 421 deletions(-) delete mode 100644 frontend/src/__tests__/PWAInstallPrompt.test.tsx delete mode 100644 frontend/src/__tests__/contacts/ContactEditModal.test.tsx delete mode 100644 frontend/src/__tests__/tags/BulkTagDialog.test.tsx delete mode 100644 frontend/src/__tests__/tags/TagPicker.test.tsx delete mode 100644 frontend/src/__tests__/tags/TagPicker.validation.test.tsx diff --git a/frontend/src/__tests__/PWAInstallPrompt.test.tsx b/frontend/src/__tests__/PWAInstallPrompt.test.tsx deleted file mode 100644 index b713fde..0000000 --- a/frontend/src/__tests__/PWAInstallPrompt.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/** - * PWA Install Prompt tests — Task 5.24. - */ - -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { PWAInstallPrompt } from '@/components/PWAInstallPrompt'; -import { getNotificationPermission, isPWAInstalled } from '@/utils/notifications'; - -// Mock i18n -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key: string) => key }), -})); - -describe('PWAInstallPrompt', () => { - beforeEach(() => { - localStorage.clear(); - vi.restoreAllMocks(); - }); - - it('renders nothing when no beforeinstallprompt event fires', () => { - render(); - expect(screen.queryByTestId('pwa-install-prompt')).toBeNull(); - }); - - it('shows install prompt when beforeinstallprompt fires', async () => { - render(); - - const event = new Event('beforeinstallprompt'); - Object.assign(event, { - prompt: vi.fn().mockResolvedValue(undefined), - userChoice: Promise.resolve({ outcome: 'accepted' }), - }); - - window.dispatchEvent(event); - - await waitFor(() => { - expect(screen.getByTestId('pwa-install-prompt')).toBeInTheDocument(); - }); - expect(screen.getByTestId('pwa-install-btn')).toBeInTheDocument(); - expect(screen.getByTestId('pwa-dismiss-btn')).toBeInTheDocument(); - }); - - it('hides when dismiss button is clicked', async () => { - render(); - - const event = new Event('beforeinstallprompt'); - Object.assign(event, { - prompt: vi.fn().mockResolvedValue(undefined), - userChoice: Promise.resolve({ outcome: 'dismissed' }), - }); - - window.dispatchEvent(event); - - await waitFor(() => { - expect(screen.getByTestId('pwa-install-prompt')).toBeInTheDocument(); - }); - - fireEvent.click(screen.getByTestId('pwa-dismiss-btn')); - - await waitFor(() => { - expect(screen.queryByTestId('pwa-install-prompt')).toBeNull(); - }); - - // Should not show again after dismiss (localStorage) - expect(localStorage.getItem('leocrm_pwa_install_dismissed')).toBe('1'); - }); - - it('does not show when already dismissed', () => { - localStorage.setItem('leocrm_pwa_install_dismissed', '1'); - render(); - - const event = new Event('beforeinstallprompt'); - Object.assign(event, { - prompt: vi.fn(), - userChoice: Promise.resolve({ outcome: 'dismissed' }), - }); - - window.dispatchEvent(event); - - expect(screen.queryByTestId('pwa-install-prompt')).toBeNull(); - }); -}); - -describe('Notification helpers', () => { - it('getNotificationPermission returns unsupported when Notification API missing', () => { - const original = (window as any).Notification; - delete (window as any).Notification; - expect(getNotificationPermission()).toBe('unsupported'); - (window as any).Notification = original; - }); - - it('isPWAInstalled returns false in browser mode', () => { - expect(isPWAInstalled()).toBe(false); - }); -}); diff --git a/frontend/src/__tests__/contacts/ContactEditModal.test.tsx b/frontend/src/__tests__/contacts/ContactEditModal.test.tsx deleted file mode 100644 index d8dac05..0000000 --- a/frontend/src/__tests__/contacts/ContactEditModal.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; - -// ContactEditModal imports UI components that transitively load lucide-react. -// To avoid OOM in the vitest worker, we test via a stub that validates the -// component's interface contract (props, rendering, callbacks). -vi.mock('@/components/contacts/ContactEditModal', () => ({ - ContactEditModal: ({ open, onClose, contact, onSaved }: any) => { - if (!open) return null; - const isEdit = !!contact; - return ( -
-

{isEdit ? 'Kontakt bearbeiten' : 'Kontakt erstellen'}

- - - - - - -
- ); - }, -})); - -import { ContactEditModal } from '@/components/contacts/ContactEditModal'; - -const defaultProps = { - open: true, - onClose: vi.fn(), - contact: null, - onSaved: vi.fn(), -}; - -describe('ContactEditModal', () => { - it('renders modal when open', () => { - render(); - expect(screen.getByTestId('modal-stub')).toBeInTheDocument(); - }); - - it('does not render when closed', () => { - render(); - expect(screen.queryByTestId('modal-stub')).not.toBeInTheDocument(); - }); - - it('renders type select', () => { - render(); - expect(screen.getByTestId('contact-type-select')).toBeInTheDocument(); - }); - - it('renders name input for company type', () => { - render(); - expect(screen.getByTestId('contact-name-input')).toBeInTheDocument(); - }); - - it('renders submit button', () => { - render(); - expect(screen.getByTestId('contact-submit-btn')).toBeInTheDocument(); - }); - - it('renders cancel button', () => { - render(); - expect(screen.getByText(/abbrechen/i)).toBeInTheDocument(); - }); - - it('calls onClose when cancel button clicked', () => { - render(); - fireEvent.click(screen.getByText(/abbrechen/i)); - expect(defaultProps.onClose).toHaveBeenCalled(); - }); - - it('renders create title for new contact', () => { - render(); - expect(screen.getByText('Kontakt erstellen')).toBeInTheDocument(); - }); - - it('renders edit title when editing existing contact', () => { - render(); - expect(screen.getByText('Kontakt bearbeiten')).toBeInTheDocument(); - }); -}); diff --git a/frontend/src/__tests__/tags/BulkTagDialog.test.tsx b/frontend/src/__tests__/tags/BulkTagDialog.test.tsx deleted file mode 100644 index c944fd6..0000000 --- a/frontend/src/__tests__/tags/BulkTagDialog.test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; - -vi.mock('@/api/tags', () => ({ - fetchTags: vi.fn(), - bulkAssignTags: vi.fn(), -})); - -vi.mock('@/components/ui/Toast', () => ({ - useToast: () => ({ success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }), -})); - -import { BulkTagDialog } from '@/components/tags/BulkTagDialog'; -import { fetchTags, bulkAssignTags } from '@/api/tags'; - -const mockTags = [ - { id: 't1', name: 'VIP-Kunde', color: '#EF4444', created_by: 'u1', usage_count: 5 }, - { id: 't2', name: 'Lead', color: '#3B82F6', created_by: 'u1', usage_count: 12 }, -]; - -beforeEach(() => { - vi.clearAllMocks(); - vi.mocked(fetchTags).mockResolvedValue(mockTags); - vi.mocked(bulkAssignTags).mockResolvedValue(undefined); -}); - -describe('BulkTagDialog', () => { - it('renders dialog when open', async () => { - render(); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - }); - - it('shows selected entity count', async () => { - render(); - expect(screen.getByText('3 ausgewaehlte Eintraege')).toBeInTheDocument(); - }); - - it('loads and displays available tags', async () => { - render(); - await waitFor(() => { - expect(fetchTags).toHaveBeenCalled(); - }); - await waitFor(() => { - expect(screen.getByRole('button', { name: /VIP-Kunde/ })).toBeInTheDocument(); - }); - expect(screen.getByRole('button', { name: /Lead/ })).toBeInTheDocument(); - }); - - it('toggles tag selection on click', async () => { - render(); - await waitFor(() => { - expect(fetchTags).toHaveBeenCalled(); - }); - const tagBtn = await screen.findByRole('button', { name: /VIP-Kunde/ }); - fireEvent.click(tagBtn); - await waitFor(() => { - expect(screen.getByText('1 Tags auswaehlen')).toBeInTheDocument(); - }); - }); - - it('calls bulkAssignTags on assign button click', async () => { - const onAssigned = vi.fn(); - const onClose = vi.fn(); - render(); - await waitFor(() => { - expect(fetchTags).toHaveBeenCalled(); - }); - const tagBtn = await screen.findByRole('button', { name: /VIP-Kunde/ }); - fireEvent.click(tagBtn); - const assignBtn = screen.getByRole('button', { name: 'Tags zuweisen' }); - fireEvent.click(assignBtn); - await waitFor(() => { - expect(bulkAssignTags).toHaveBeenCalledWith({ - tag_ids: ['t1'], - entity_type: 'contact', - entity_ids: ['c1', 'c2'], - }); - }); - }); - - it('does not render when closed', () => { - render(); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); -}); diff --git a/frontend/src/__tests__/tags/TagPicker.test.tsx b/frontend/src/__tests__/tags/TagPicker.test.tsx deleted file mode 100644 index f26e40d..0000000 --- a/frontend/src/__tests__/tags/TagPicker.test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, waitFor, fireEvent } from '@testing-library/react'; - -const mockFetchTags = vi.fn(); -const mockAssignTag = vi.fn(); -const mockUnassignTag = vi.fn(); -const mockCreateTag = vi.fn(); - -vi.mock('@/api/tags', () => ({ - fetchTags: (...args: any[]) => mockFetchTags(...args), - assignTag: (...args: any[]) => mockAssignTag(...args), - unassignTag: (...args: any[]) => mockUnassignTag(...args), - createTag: (...args: any[]) => mockCreateTag(...args), -})); - -import { TagPicker } from '@/components/tags/TagPicker'; - -const mockTags = [ - { id: 't1', name: 'VIP-Kunde', color: '#10B981', created_by: 'u1' }, - { id: 't2', name: 'Lead', color: '#3B82F6', created_by: 'u1' }, - { id: 't3', name: 'Archiv', color: '#6B7280', created_by: 'u1' }, -]; - -beforeEach(() => { - vi.clearAllMocks(); - mockFetchTags.mockResolvedValue(mockTags); - mockAssignTag.mockResolvedValue({ id: 'a1', tag_id: 't1', entity_type: 'contact', entity_id: 'c1' }); - mockUnassignTag.mockResolvedValue(undefined); - mockCreateTag.mockResolvedValue({ id: 't4', name: 'Neu', color: '#F59E0B', created_by: 'u1' }); -}); - -describe('TagPicker', () => { - it('renders the tag picker container', async () => { - render(); - expect(screen.getByTestId('tag-picker')).toBeInTheDocument(); - }); - - it('renders assigned tags section', async () => { - render(); - expect(screen.getByText('Zugewiesene Tags')).toBeInTheDocument(); - }); - - it('shows no tags assigned message when empty', async () => { - render(); - await waitFor(() => { - expect(screen.getByText('Keine Tags zugewiesen')).toBeInTheDocument(); - }); - }); - - it('loads and displays available tags', async () => { - render(); - await waitFor(() => { - expect(mockFetchTags).toHaveBeenCalled(); - }); - await waitFor(() => { - expect(screen.getByTestId('available-tags-list')).toBeInTheDocument(); - }); - expect(screen.getByText('VIP-Kunde')).toBeInTheDocument(); - expect(screen.getByText('Lead')).toBeInTheDocument(); - }); - - it('assigns a tag when clicking available tag', async () => { - render(); - await waitFor(() => { - expect(screen.getByText('VIP-Kunde')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByText('VIP-Kunde')); - await waitFor(() => { - expect(mockAssignTag).toHaveBeenCalledWith({ tag_id: 't1', entity_type: 'contact', entity_id: 'c1' }); - }); - }); - - it('shows create tag form when clicking create button', async () => { - render(); - await waitFor(() => { - expect(screen.getByText('Neues Tag')).toBeInTheDocument(); - }); - fireEvent.click(screen.getByText('Neues Tag')); - await waitFor(() => { - expect(screen.getByTestId('create-tag-form')).toBeInTheDocument(); - }); - }); - - it('renders search input for tags', async () => { - render(); - expect(screen.getByLabelText('Tag suchen')).toBeInTheDocument(); - }); -}); diff --git a/frontend/src/__tests__/tags/TagPicker.validation.test.tsx b/frontend/src/__tests__/tags/TagPicker.validation.test.tsx deleted file mode 100644 index c5e84df..0000000 --- a/frontend/src/__tests__/tags/TagPicker.validation.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; - -vi.mock('@/api/tags', () => ({ - fetchTags: vi.fn().mockResolvedValue([]), - assignTag: vi.fn().mockResolvedValue({}), - unassignTag: vi.fn().mockResolvedValue({}), - createTag: vi.fn().mockResolvedValue({ id: 't1', name: 'NewTag', color: '#3B82F6' }), -})); - -vi.mock('@/components/ui/Toast', () => ({ - useToast: () => ({ success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }), -})); - -import { TagPicker } from '@/components/tags/TagPicker'; -import { createTag } from '@/api/tags'; - -beforeEach(() => { - vi.clearAllMocks(); -}); - -describe('TagPicker validation (RHF + Zod)', () => { - it('shows validation error when tag name is empty', async () => { - render(); - // Wait for loading to finish and find create button - await waitFor(() => { - expect(screen.getByTestId('tag-picker')).toBeInTheDocument(); - }, { timeout: 3000 }); - // Click create button to show form (German: 'Neues Tag') - const createBtn = screen.getByText(/neues tag|create/i); - fireEvent.click(createBtn); - // Submit empty form - await waitFor(() => { - expect(screen.getByTestId('create-tag-form')).toBeInTheDocument(); - }); - const form = screen.getByTestId('create-tag-form').querySelector('form'); - expect(form).toBeTruthy(); - fireEvent.submit(form!); - await waitFor(() => { - const errors = screen.getAllByText(/erforderlich|required/i); - expect(errors.length).toBeGreaterThan(0); - }); - }); - - it('calls createTag when form is valid', async () => { - render(); - await waitFor(() => { - expect(screen.getByTestId('tag-picker')).toBeInTheDocument(); - }, { timeout: 3000 }); - const createBtn = screen.getByText(/neues tag|create/i); - fireEvent.click(createBtn); - await waitFor(() => { - expect(screen.getByTestId('create-tag-form')).toBeInTheDocument(); - }); - // Fill in tag name - const form = screen.getByTestId('create-tag-form').querySelector('form'); - const input = form!.querySelector('input'); - expect(input).toBeTruthy(); - fireEvent.change(input!, { target: { value: 'Important' } }); - fireEvent.submit(form!); - await waitFor(() => { - expect(createTag).toHaveBeenCalledWith(expect.objectContaining({ name: 'Important' })); - }, { timeout: 3000 }); - }); -}); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 4194d2b..639edc6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -48,6 +48,7 @@ export default defineConfig({ environment: 'jsdom', setupFiles: 'src/test/setup.ts', css: true, + exclude: ['**/node_modules/**', '**/e2e/**', '**/test-results/**'], coverage: { provider: 'v8', reporter: ['text', 'json-summary'],