Files
tfm_ainventory/frontend/tests/components/PhotoModal.test.tsx
Daniel Bedeleanu 9a87064dbe fix(design): replace all 40+ hardcoded colors with DESIGN.md semantic system
Phase 10 Implementation: Bulk color system compliance across frontend

Applied DESIGN_COLOR_RULES.md to 40 component and page files:
- Indigo (3) → primary/secondary (primary actions)
- Green (12) → tertiary (#00e639) (success/healthy status)
- Red/Rose (20) → error (#ffb4ab) (destructive actions)
- Amber/Sky (9) → primary/secondary (warnings/info)
- Blue/Slate (various) → primary/design system colors (focus states)
- Black → bg-surface-container-lowest (proper design color)

Files updated: 40 components + pages
Color replacements: 44+ instances
Build status: ✓ Zero errors, compiled in 6.3s
Test status: Pending (npm run test)

All colors now follow DESIGN.md Industrial Precision system:
 Primary: #ffb781 (caution orange)
 Secondary: #c8c6c5 (gray)
 Tertiary: #00e639 (healthy green)
 Error: #ffb4ab (destructive red)
 Design system enforced across all UI/UX

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-25 17:19:53 +03:00

274 lines
7.0 KiB
TypeScript

import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import PhotoModal from '@/components/PhotoModal';
describe('PhotoModal', () => {
const mockOnClose = vi.fn();
const mockPhotoUrl = 'https://example.com/photo.jpg';
beforeEach(() => {
mockOnClose.mockClear();
});
describe('Rendering', () => {
it('should render photo modal with image', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const image = screen.getByAltText('Test Item');
expect(image).toBeInTheDocument();
expect(image).toHaveAttribute('src', mockPhotoUrl);
});
it('should display title in header', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
expect(screen.getByText('Test Item')).toBeInTheDocument();
});
it('should render with default title if not provided', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
/>
);
expect(screen.getByText('Photo')).toBeInTheDocument();
});
it('should render close button with rose color', () => {
const { container } = render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const closeButton = screen.getByLabelText('Close modal');
expect(closeButton).toBeInTheDocument();
// Check for rose-colored X icon
const xIcon = closeButton.querySelector('svg');
expect(xIcon).toHaveClass('text-error');
});
});
describe('Close Interactions', () => {
it('should close when clicking close button', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const closeButton = screen.getByLabelText('Close modal');
fireEvent.click(closeButton);
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
it('should close when clicking outside modal (on backdrop)', () => {
const { container } = render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
// Find the backdrop element (has onClick handler directly)
const backdrop = container.querySelector('[role="dialog"][class*="fixed"][class*="inset-0"]');
if (backdrop) {
fireEvent.click(backdrop);
expect(mockOnClose).toHaveBeenCalledTimes(1);
}
});
it('should NOT close when clicking on modal image', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const image = screen.getByAltText('Test Item');
fireEvent.click(image);
expect(mockOnClose).not.toHaveBeenCalled();
});
it('should close when pressing Escape key', async () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
fireEvent.keyDown(window, { key: 'Escape' });
await waitFor(() => {
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});
it('should NOT close when pressing other keys', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
fireEvent.keyDown(window, { key: 'Enter' });
expect(mockOnClose).not.toHaveBeenCalled();
});
});
describe('Image Properties', () => {
it('should have lazy loading enabled', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const image = screen.getByAltText('Test Item');
expect(image).toHaveAttribute('loading', 'lazy');
});
it('should have object-contain class for proper scaling', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const image = screen.getByAltText('Test Item');
expect(image).toHaveClass('object-contain');
});
it('should render image with proper max-height constraint', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const image = screen.getByAltText('Test Item');
// Image has max-h constraint
expect(image).toHaveClass('max-h-[calc(90vh-120px)]');
});
});
describe('Accessibility', () => {
it('should have proper ARIA attributes for dialog', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const dialog = screen.getByRole('dialog');
expect(dialog).toHaveAttribute('aria-modal', 'true');
expect(dialog).toHaveAttribute('aria-label', 'Photo viewer for Test Item');
});
it('should have accessible close button', () => {
render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const closeButton = screen.getByLabelText('Close modal');
expect(closeButton).toBeInTheDocument();
});
});
describe('Responsive Design', () => {
it('should have responsive max-width classes', () => {
const { container } = render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
const backdrop = screen.getByRole('dialog');
// The modal content div is the second child of backdrop
const modalContent = backdrop.querySelector('div[class*="rounded-none"]');
expect(modalContent).toHaveClass('max-w-2xl', 'w-full', 'max-h-[90vh]');
});
it('should have responsive padding in header', () => {
const { container } = render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
// Header should have responsive padding
const header = screen.getByText('Test Item').closest('div');
expect(header).toHaveClass('p-4', 'md:p-6');
});
});
describe('Cleanup', () => {
it('should remove keyboard listener on unmount', () => {
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
const { unmount } = render(
<PhotoModal
photoUrl={mockPhotoUrl}
onClose={mockOnClose}
title="Test Item"
/>
);
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledWith(
'keydown',
expect.any(Function)
);
removeEventListenerSpy.mockRestore();
});
});
});