import React from 'react' import { render, screen, fireEvent } from '@testing-library/react' import { NotificationItem } from '../../../src/renderer/components/notifications/NotificationItem' import { useNotificationStore } from '../../../src/renderer/stores/notificationStore' import { factories } from '../../utils/testHelpers' /** * Mock the notification store */ jest.mock('../../../src/renderer/stores/notificationStore', () => ({ useNotificationStore: jest.fn(), })) /** * Mock framer-motion to simplify testing */ jest.mock('framer-motion', () => ({ motion: { div: ({ children, className, role, ...props }: { children: React.ReactNode; className?: string; role?: string; [key: string]: unknown }) => (
{children}
), }, })) /** * Mock Typography component */ jest.mock('../../../src/renderer/components/ui/Typography', () => ({ __esModule: true, default: ({ children, variant, color, className }: { children: React.ReactNode; variant?: string; color?: string; className?: string }) => ( {children} ), })) describe('NotificationItem', () => { const mockRemoveNotification = jest.fn() beforeEach(() => { ;(useNotificationStore as jest.Mock).mockReturnValue(mockRemoveNotification) jest.clearAllMocks() }) describe('rendering', () => { it('should render notification with title', () => { const notification = factories.notification({ title: 'Test Notification', type: 'info', }) render() expect(screen.getByText('Test Notification')).toBeInTheDocument() }) it('should render notification with title and message', () => { const notification = factories.notification({ title: 'Test Notification', message: 'This is a test message', type: 'info', }) render() expect(screen.getByText('Test Notification')).toBeInTheDocument() expect(screen.getByText('This is a test message')).toBeInTheDocument() }) it('should render notification without message when message is not provided', () => { const notification = factories.notification({ title: 'Test Notification', message: undefined, type: 'info', }) render() expect(screen.getByText('Test Notification')).toBeInTheDocument() expect(screen.queryByText('This is a test message')).not.toBeInTheDocument() }) }) describe('notification types and icons', () => { const testCases = [ { type: 'success' as const, expectedColor: 'text-green-500', expectedBorder: 'border-green-500' }, { type: 'error' as const, expectedColor: 'text-red-500', expectedBorder: 'border-red-500' }, { type: 'warning' as const, expectedColor: 'text-yellow-500', expectedBorder: 'border-yellow-500' }, { type: 'info' as const, expectedColor: 'text-blue-500', expectedBorder: 'border-blue-500' }, ] testCases.forEach(({ type, expectedColor, expectedBorder }) => { it(`should render ${type} notification with correct icon and styling`, () => { const notification = factories.notification({ title: `${type} notification`, type, }) const { container } = render() const icon = container.querySelector(`.${expectedColor}`) expect(icon).toBeInTheDocument() const notificationContainer = container.querySelector(`.${expectedBorder}`) expect(notificationContainer).toBeInTheDocument() }) }) it('should default to info type when type is not recognized', () => { const notification = { ...factories.notification(), type: 'unknown' as 'info' | 'success' | 'warning' | 'error', } const { container } = render() const icon = container.querySelector('.text-blue-500') expect(icon).toBeInTheDocument() const notificationContainer = container.querySelector('.border-blue-500') expect(notificationContainer).toBeInTheDocument() }) }) describe('dismiss functionality', () => { it('should have dismiss button', () => { const notification = factories.notification({ title: 'Test Notification', type: 'info', }) render() const dismissButton = screen.getByRole('button', { name: /dismiss notification/i }) expect(dismissButton).toBeInTheDocument() }) it('should call removeNotification when dismiss button is clicked', () => { const notification = factories.notification({ id: 'test-notification-123', title: 'Test Notification', type: 'info', }) render() const dismissButton = screen.getByRole('button', { name: /dismiss notification/i }) fireEvent.click(dismissButton) expect(mockRemoveNotification).toHaveBeenCalledWith('test-notification-123') expect(mockRemoveNotification).toHaveBeenCalledTimes(1) }) it('should have correct aria-label for accessibility', () => { const notification = factories.notification({ title: 'Test Notification', type: 'info', }) render() const dismissButton = screen.getByLabelText('Dismiss notification') expect(dismissButton).toBeInTheDocument() }) }) describe('accessibility', () => { it('should have proper role attribute', () => { const notification = factories.notification({ title: 'Test Notification', type: 'info', }) render() const alertElement = screen.getByRole('alert') expect(alertElement).toBeInTheDocument() }) it('should be keyboard accessible', () => { const notification = factories.notification({ title: 'Test Notification', type: 'info', }) render() const dismissButton = screen.getByRole('button', { name: /dismiss notification/i }) dismissButton.focus() expect(document.activeElement).toBe(dismissButton) }) }) describe('styling and layout', () => { it('should have correct base classes', () => { const notification = factories.notification({ title: 'Test Notification', type: 'info', }) const { container } = render() const notificationElement = container.firstChild as HTMLElement expect(notificationElement).toHaveClass('pointer-events-auto') expect(notificationElement).toHaveClass('mb-3') expect(notificationElement).toHaveClass('bg-white') expect(notificationElement).toHaveClass('dark:bg-gray-800') expect(notificationElement).toHaveClass('rounded-lg') expect(notificationElement).toHaveClass('shadow-lg') expect(notificationElement).toHaveClass('border-l-4') expect(notificationElement).toHaveClass('p-4') expect(notificationElement).toHaveClass('min-w-[320px]') expect(notificationElement).toHaveClass('max-w-md') }) it('should have proper flexbox layout', () => { const notification = factories.notification({ title: 'Test Notification', message: 'Test message', type: 'info', }) const { container } = render() const flexContainer = container.querySelector('.flex.items-start') expect(flexContainer).toBeInTheDocument() const iconContainer = container.querySelector('.flex-shrink-0') expect(iconContainer).toBeInTheDocument() const contentContainer = container.querySelector('.flex-1') expect(contentContainer).toBeInTheDocument() }) }) describe('content layout', () => { it('should have proper spacing between elements', () => { const notification = factories.notification({ title: 'Test Notification', message: 'Test message', type: 'info', }) const { container } = render() expect(container.querySelector('.ml-3')).toBeInTheDocument() expect(container.querySelector('.mt-1')).toBeInTheDocument() expect(container.querySelector('.ml-4')).toBeInTheDocument() }) it('should handle long titles gracefully', () => { const notification = factories.notification({ title: 'This is a very long notification title that might wrap to multiple lines', type: 'info', }) render() expect(screen.getByText(/This is a very long notification title/)).toBeInTheDocument() }) it('should handle long messages gracefully', () => { const notification = factories.notification({ title: 'Test Notification', message: 'This is a very long notification message that contains a lot of text and might wrap to multiple lines to test the layout behavior', type: 'info', }) render() expect(screen.getByText(/This is a very long notification message/)).toBeInTheDocument() }) }) describe('edge cases', () => { it('should handle empty title gracefully', () => { const notification = factories.notification({ title: '', message: 'Message without title', type: 'info', }) render() expect(screen.getByText('Message without title')).toBeInTheDocument() }) it('should handle special characters in title and message', () => { const notification = factories.notification({ title: 'Special chars: <>&"\'', message: 'Message with special chars: <>&"\'', type: 'info', }) render() expect(screen.getByText('Special chars: <>&"\'')).toBeInTheDocument() expect(screen.getByText('Message with special chars: <>&"\'')).toBeInTheDocument() }) }) })