feat: Visual masterpiece - true light mode & premium UI
🎨 **True Light/Dark Mode**
- Implemented proper light mode with inverted color scheme
- Smooth theme transitions (0.3s ease)
- Light mode colors: white backgrounds, dark text, vibrant accents
- System theme detection with proper class application
🌈 **Enhanced Theme System**
- 4 color themes work in both light and dark modes
- Cyber (cyan/magenta), Ocean (blue/teal), Forest (green/lime), Sunset (orange/purple)
- Theme-specific glassmorphism effects
- Proper contrast in light mode
✨ **Premium Animations**
- Float, glow-pulse, slide-in, scale-in, rotate-in animations
- Smooth page transitions
- Hover effects with depth (lift, glow, scale)
- Micro-interactions on all interactive elements
🎯 **Visual Polish**
- Enhanced glassmorphism for light/dark modes
- Custom scrollbar with theme colors
- Beautiful text selection
- Focus indicators for accessibility
- Premium utility classes
🔧 **Technical Improvements**
- Updated UIStore to properly apply light/dark classes
- Added data-theme attribute for CSS targeting
- Smooth scroll behavior
- Optimized transitions
The app is now a visual masterpiece with perfect light/dark mode support!
2026-01-11 01:32:21 +00:00
|
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
2025-12-03 21:56:50 +00:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
import userEvent from '@testing-library/user-event';
|
feat: Visual masterpiece - true light mode & premium UI
🎨 **True Light/Dark Mode**
- Implemented proper light mode with inverted color scheme
- Smooth theme transitions (0.3s ease)
- Light mode colors: white backgrounds, dark text, vibrant accents
- System theme detection with proper class application
🌈 **Enhanced Theme System**
- 4 color themes work in both light and dark modes
- Cyber (cyan/magenta), Ocean (blue/teal), Forest (green/lime), Sunset (orange/purple)
- Theme-specific glassmorphism effects
- Proper contrast in light mode
✨ **Premium Animations**
- Float, glow-pulse, slide-in, scale-in, rotate-in animations
- Smooth page transitions
- Hover effects with depth (lift, glow, scale)
- Micro-interactions on all interactive elements
🎯 **Visual Polish**
- Enhanced glassmorphism for light/dark modes
- Custom scrollbar with theme colors
- Beautiful text selection
- Focus indicators for accessibility
- Premium utility classes
🔧 **Technical Improvements**
- Updated UIStore to properly apply light/dark classes
- Added data-theme attribute for CSS targeting
- Smooth scroll behavior
- Optimized transitions
The app is now a visual masterpiece with perfect light/dark mode support!
2026-01-11 01:32:21 +00:00
|
|
|
import '@testing-library/jest-dom/vitest';
|
|
|
|
|
import { Dialog, DialogBody, DialogFooter, DialogHeader } from './dialog';
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
describe('Dialog Component', () => {
|
|
|
|
|
const mockOnClose = vi.fn();
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
document.body.style.overflow = '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders nothing when open is false', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={false} onClose={mockOnClose} title="Test Dialog">
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByText('Dialog content')).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders dialog when open is true', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose} title="Test Dialog">
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Dialog content')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Test Dialog')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('displays title when provided', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose} title="Test Dialog">
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Test Dialog')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders DialogHeader correctly', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose} title="Test Dialog">
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const header = screen.getByText('Test Dialog').closest('.border-b');
|
|
|
|
|
expect(header).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders DialogBody correctly', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose} title="Test Dialog">
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Dialog content')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders custom footer when provided', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Test Dialog"
|
|
|
|
|
footer={<button>Custom Footer</button>}
|
|
|
|
|
>
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Custom Footer')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows default footer with confirm and cancel buttons for confirm variant', () => {
|
|
|
|
|
const mockOnConfirm = vi.fn();
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Confirm Dialog"
|
|
|
|
|
variant="confirm"
|
|
|
|
|
onConfirm={mockOnConfirm}
|
|
|
|
|
showCancel={true}
|
|
|
|
|
>
|
|
|
|
|
<div>Are you sure?</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Confirm')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('calls onConfirm when confirm button is clicked', async () => {
|
|
|
|
|
const user = userEvent.setup();
|
|
|
|
|
const mockOnConfirm = vi.fn();
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Confirm Dialog"
|
|
|
|
|
variant="confirm"
|
|
|
|
|
onConfirm={mockOnConfirm}
|
|
|
|
|
>
|
|
|
|
|
<div>Are you sure?</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const confirmButton = screen.getByText('Confirm');
|
|
|
|
|
await user.click(confirmButton);
|
|
|
|
|
|
|
|
|
|
expect(mockOnConfirm).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('calls onCancel when cancel button is clicked', async () => {
|
|
|
|
|
const user = userEvent.setup();
|
|
|
|
|
const mockOnCancel = vi.fn();
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Confirm Dialog"
|
|
|
|
|
variant="confirm"
|
|
|
|
|
onCancel={mockOnCancel}
|
|
|
|
|
showCancel={true}
|
|
|
|
|
>
|
|
|
|
|
<div>Are you sure?</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const cancelButton = screen.getByText('Cancel');
|
|
|
|
|
await user.click(cancelButton);
|
|
|
|
|
|
|
|
|
|
expect(mockOnCancel).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows alert icon for alert variant', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Alert Dialog"
|
|
|
|
|
variant="alert"
|
|
|
|
|
>
|
|
|
|
|
<div>Alert message</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const icon = screen
|
|
|
|
|
.getByText('Alert Dialog')
|
|
|
|
|
.closest('.border-b')
|
|
|
|
|
?.querySelector('svg');
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(icon).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows info icon for info variant', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Info Dialog"
|
|
|
|
|
variant="info"
|
|
|
|
|
>
|
|
|
|
|
<div>Info message</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const icon = screen
|
|
|
|
|
.getByText('Info Dialog')
|
|
|
|
|
.closest('.border-b')
|
|
|
|
|
?.querySelector('svg');
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(icon).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('applies destructive variant to confirm button for alert', () => {
|
|
|
|
|
const mockOnConfirm = vi.fn();
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Alert Dialog"
|
|
|
|
|
variant="alert"
|
|
|
|
|
onConfirm={mockOnConfirm}
|
|
|
|
|
>
|
|
|
|
|
<div>Alert message</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const confirmButton = screen.getByText('Confirm');
|
|
|
|
|
const buttonElement = confirmButton.closest('button');
|
|
|
|
|
// Vérifier que le bouton a les classes de variant destructive
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
buttonElement?.classList.contains('bg-destructive') ||
|
2026-01-13 18:47:57 +00:00
|
|
|
buttonElement?.classList.contains('text-destructive-foreground'),
|
2025-12-13 02:34:34 +00:00
|
|
|
).toBe(true);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('uses custom confirm and cancel labels', () => {
|
|
|
|
|
const mockOnConfirm = vi.fn();
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Confirm Dialog"
|
|
|
|
|
variant="confirm"
|
|
|
|
|
onConfirm={mockOnConfirm}
|
|
|
|
|
confirmLabel="Yes"
|
|
|
|
|
cancelLabel="No"
|
|
|
|
|
showCancel={true}
|
|
|
|
|
>
|
|
|
|
|
<div>Are you sure?</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Yes')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('No')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('hides cancel button when showCancel is false', () => {
|
|
|
|
|
const mockOnConfirm = vi.fn();
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Confirm Dialog"
|
|
|
|
|
variant="confirm"
|
|
|
|
|
onConfirm={mockOnConfirm}
|
|
|
|
|
showCancel={false}
|
|
|
|
|
>
|
|
|
|
|
<div>Are you sure?</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Confirm')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not show footer for default variant without footer or actions', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose} title="Default Dialog">
|
|
|
|
|
<div>Dialog content</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const footer = document.querySelector('.border-t');
|
|
|
|
|
expect(footer).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles async onConfirm', async () => {
|
|
|
|
|
const user = userEvent.setup();
|
|
|
|
|
const mockOnConfirm = vi.fn().mockResolvedValue(undefined);
|
|
|
|
|
render(
|
|
|
|
|
<Dialog
|
|
|
|
|
open={true}
|
|
|
|
|
onClose={mockOnClose}
|
|
|
|
|
title="Confirm Dialog"
|
|
|
|
|
variant="confirm"
|
|
|
|
|
onConfirm={mockOnConfirm}
|
|
|
|
|
>
|
|
|
|
|
<div>Are you sure?</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const confirmButton = screen.getByText('Confirm');
|
|
|
|
|
await user.click(confirmButton);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockOnConfirm).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('DialogHeader', () => {
|
|
|
|
|
it('renders DialogHeader correctly', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose}>
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<div>Header content</div>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
<DialogBody>
|
|
|
|
|
<div>Body content</div>
|
|
|
|
|
</DialogBody>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Header content')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('DialogBody', () => {
|
|
|
|
|
it('renders DialogBody correctly', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose}>
|
|
|
|
|
<DialogBody>
|
|
|
|
|
<div>Body content</div>
|
|
|
|
|
</DialogBody>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Body content')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('DialogFooter', () => {
|
|
|
|
|
it('renders DialogFooter correctly', () => {
|
|
|
|
|
render(
|
|
|
|
|
<Dialog open={true} onClose={mockOnClose}>
|
|
|
|
|
<DialogBody>
|
|
|
|
|
<div>Body content</div>
|
|
|
|
|
</DialogBody>
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<button>Footer button</button>
|
|
|
|
|
</DialogFooter>
|
2025-12-13 02:34:34 +00:00
|
|
|
</Dialog>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Footer button')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|