Goose Simple Compact UX (#4202)
Co-authored-by: David Katz <dkatz@squareup.com>
This commit is contained in:
@@ -12,6 +12,7 @@ const alertIcons: Record<AlertType, React.ReactNode> = {
|
||||
interface AlertBoxProps {
|
||||
alert: Alert;
|
||||
className?: string;
|
||||
compactButtonEnabled?: boolean;
|
||||
}
|
||||
|
||||
const alertStyles: Record<AlertType, string> = {
|
||||
@@ -60,17 +61,23 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
|
||||
: alert.progress!.total}
|
||||
</span>
|
||||
</div>
|
||||
{alert.showSummarizeButton && alert.onSummarize && (
|
||||
{alert.showCompactButton && alert.onCompact && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
alert.onSummarize!();
|
||||
alert.onCompact!();
|
||||
}}
|
||||
className="flex items-center gap-1.5 text-[11px] hover:opacity-80 cursor-pointer outline-none mt-1"
|
||||
disabled={alert.compactButtonDisabled}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 text-[11px] outline-none mt-1',
|
||||
alert.compactButtonDisabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: 'hover:opacity-80 cursor-pointer'
|
||||
)}
|
||||
>
|
||||
{alert.summarizeIcon}
|
||||
<span>Summarize now</span>
|
||||
{alert.compactIcon}
|
||||
<span>Compact now</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AlertBox } from '../AlertBox';
|
||||
import { Alert, AlertType } from '../types';
|
||||
|
||||
describe('AlertBox', () => {
|
||||
const mockOnCompact = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Basic Rendering', () => {
|
||||
it('should render info alert with message', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Test info message',
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('Test info message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render warning alert with correct styling', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Warning,
|
||||
message: 'Test warning message',
|
||||
};
|
||||
|
||||
const { container } = render(<AlertBox alert={alert} />);
|
||||
const alertElement = container.querySelector('.bg-\\[\\#cc4b03\\]');
|
||||
|
||||
expect(alertElement).toBeInTheDocument();
|
||||
expect(screen.getByText('Test warning message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error alert with correct styling', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Error,
|
||||
message: 'Test error message',
|
||||
};
|
||||
|
||||
const { container } = render(<AlertBox alert={alert} />);
|
||||
const alertElement = container.querySelector('.bg-\\[\\#d7040e\\]');
|
||||
|
||||
expect(alertElement).toBeInTheDocument();
|
||||
expect(screen.getByText('Test error message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply custom className', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Test message',
|
||||
};
|
||||
|
||||
const { container } = render(<AlertBox alert={alert} className="custom-class" />);
|
||||
const alertElement = container.firstChild as HTMLElement;
|
||||
|
||||
expect(alertElement).toHaveClass('custom-class');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Progress Bar', () => {
|
||||
it('should render progress bar when progress is provided', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 50,
|
||||
total: 100,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('50')).toBeInTheDocument();
|
||||
expect(screen.getByText('50%')).toBeInTheDocument();
|
||||
expect(screen.getByText('100')).toBeInTheDocument();
|
||||
|
||||
// Check progress bar exists
|
||||
const progressDots = screen.getByText('Context window').parentElement?.parentElement?.querySelectorAll('.h-\\[2px\\]');
|
||||
expect(progressDots).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle zero current value', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 0,
|
||||
total: 100,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('0')).toBeInTheDocument();
|
||||
expect(screen.getByText('0%')).toBeInTheDocument();
|
||||
expect(screen.getByText('100')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle 100% progress', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 100,
|
||||
total: 100,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
// Use getAllByText since there are multiple "100" elements (current and total)
|
||||
const hundredElements = screen.getAllByText('100');
|
||||
expect(hundredElements).toHaveLength(2); // One for current, one for total
|
||||
expect(screen.getByText('100%')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should format large numbers with k suffix', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 1500,
|
||||
total: 10000,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('1.5k')).toBeInTheDocument();
|
||||
expect(screen.getByText('15%')).toBeInTheDocument();
|
||||
expect(screen.getByText('10k')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle progress over 100%', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Warning,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 150,
|
||||
total: 100,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('150')).toBeInTheDocument();
|
||||
expect(screen.getByText('150%')).toBeInTheDocument();
|
||||
expect(screen.getByText('100')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Compact Button', () => {
|
||||
it('should render compact button when showCompactButton is true', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: true,
|
||||
onCompact: mockOnCompact,
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('Compact now')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render compact button with custom icon', () => {
|
||||
const CompactIcon = () => <span data-testid="compact-icon">📦</span>;
|
||||
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: true,
|
||||
onCompact: mockOnCompact,
|
||||
compactIcon: <CompactIcon />,
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByTestId('compact-icon')).toBeInTheDocument();
|
||||
expect(screen.getByText('Compact now')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onCompact when compact button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: true,
|
||||
onCompact: mockOnCompact,
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
const compactButton = screen.getByText('Compact now');
|
||||
await user.click(compactButton);
|
||||
|
||||
expect(mockOnCompact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should prevent event propagation when compact button is clicked', () => {
|
||||
const mockParentClick = vi.fn();
|
||||
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: true,
|
||||
onCompact: mockOnCompact,
|
||||
};
|
||||
|
||||
render(
|
||||
<div onClick={mockParentClick}>
|
||||
<AlertBox alert={alert} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const compactButton = screen.getByText('Compact now');
|
||||
fireEvent.click(compactButton);
|
||||
|
||||
expect(mockOnCompact).toHaveBeenCalledTimes(1);
|
||||
expect(mockParentClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not render compact button when showCompactButton is false', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: false,
|
||||
onCompact: mockOnCompact,
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.queryByText('Compact now')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render compact button when onCompact is not provided', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: true,
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.queryByText('Compact now')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Combined Features', () => {
|
||||
it('should render progress bar and compact button together', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 75,
|
||||
total: 100,
|
||||
},
|
||||
showCompactButton: true,
|
||||
onCompact: mockOnCompact,
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('75')).toBeInTheDocument();
|
||||
expect(screen.getByText('75%')).toBeInTheDocument();
|
||||
expect(screen.getByText('100')).toBeInTheDocument();
|
||||
expect(screen.getByText('Compact now')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle multiline messages', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Warning,
|
||||
message: 'Line 1\nLine 2\nLine 3',
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
// Use a function matcher to handle the whitespace-pre-line rendering
|
||||
expect(screen.getByText((content) => content.includes('Line 1') && content.includes('Line 2') && content.includes('Line 3'))).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty message', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: '',
|
||||
};
|
||||
|
||||
const { container } = render(<AlertBox alert={alert} />);
|
||||
|
||||
// Should still render the alert container
|
||||
const alertElement = container.querySelector('.flex.flex-col.gap-2');
|
||||
expect(alertElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle progress with zero total', () => {
|
||||
const alert: Alert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window',
|
||||
progress: {
|
||||
current: 10,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
|
||||
render(<AlertBox alert={alert} />);
|
||||
|
||||
expect(screen.getByText('10')).toBeInTheDocument();
|
||||
expect(screen.getByText('0')).toBeInTheDocument();
|
||||
// Progress percentage would be Infinity, but it should still render
|
||||
expect(screen.getByText('Infinity%')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,339 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useAlerts } from '../useAlerts';
|
||||
import { AlertType } from '../types';
|
||||
|
||||
describe('useAlerts', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Initial State', () => {
|
||||
it('should start with empty alerts array', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
expect(result.current.alerts).toEqual([]);
|
||||
expect(typeof result.current.addAlert).toBe('function');
|
||||
expect(typeof result.current.clearAlerts).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Adding Alerts', () => {
|
||||
it('should add a single alert', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const newAlert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Test alert',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(newAlert);
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(1);
|
||||
expect(result.current.alerts[0]).toMatchObject(newAlert);
|
||||
});
|
||||
|
||||
it('should add multiple alerts', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const alert1 = { type: AlertType.Info, message: 'First alert' };
|
||||
const alert2 = { type: AlertType.Warning, message: 'Second alert' };
|
||||
const alert3 = { type: AlertType.Error, message: 'Third alert' };
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(alert1);
|
||||
result.current.addAlert(alert2);
|
||||
result.current.addAlert(alert3);
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(3);
|
||||
expect(result.current.alerts[0]).toMatchObject(alert1);
|
||||
expect(result.current.alerts[1]).toMatchObject(alert2);
|
||||
expect(result.current.alerts[2]).toMatchObject(alert3);
|
||||
});
|
||||
|
||||
it('should add alerts with all optional properties', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const complexAlert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Complex alert',
|
||||
progress: { current: 50, total: 100 },
|
||||
showCompactButton: true,
|
||||
onCompact: vi.fn(),
|
||||
compactIcon: <span>Icon</span>,
|
||||
autoShow: true,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(complexAlert);
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(1);
|
||||
expect(result.current.alerts[0]).toMatchObject(complexAlert);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Clearing Alerts', () => {
|
||||
it('should clear all alerts', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
// Add some alerts first
|
||||
act(() => {
|
||||
result.current.addAlert({ type: AlertType.Info, message: 'Alert 1' });
|
||||
result.current.addAlert({ type: AlertType.Warning, message: 'Alert 2' });
|
||||
result.current.addAlert({ type: AlertType.Error, message: 'Alert 3' });
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(3);
|
||||
|
||||
// Clear all alerts
|
||||
act(() => {
|
||||
result.current.clearAlerts();
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(0);
|
||||
expect(result.current.alerts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle clearing when no alerts exist', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
expect(result.current.alerts).toHaveLength(0);
|
||||
|
||||
// Should not throw error
|
||||
act(() => {
|
||||
result.current.clearAlerts();
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Alert Management Patterns', () => {
|
||||
it('should handle rapid add and clear operations', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
// Rapid operations
|
||||
act(() => {
|
||||
result.current.addAlert({ type: AlertType.Info, message: 'Alert 1' });
|
||||
result.current.addAlert({ type: AlertType.Warning, message: 'Alert 2' });
|
||||
result.current.clearAlerts();
|
||||
result.current.addAlert({ type: AlertType.Error, message: 'Alert 3' });
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(1);
|
||||
expect(result.current.alerts[0].message).toBe('Alert 3');
|
||||
});
|
||||
|
||||
it('should maintain alert order', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const alerts = [
|
||||
{ type: AlertType.Info, message: 'First' },
|
||||
{ type: AlertType.Warning, message: 'Second' },
|
||||
{ type: AlertType.Error, message: 'Third' },
|
||||
{ type: AlertType.Info, message: 'Fourth' },
|
||||
];
|
||||
|
||||
act(() => {
|
||||
alerts.forEach(alert => result.current.addAlert(alert));
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(4);
|
||||
alerts.forEach((alert, index) => {
|
||||
expect(result.current.alerts[index].message).toBe(alert.message);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle duplicate alerts', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const duplicateAlert = { type: AlertType.Info, message: 'Duplicate alert' };
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(duplicateAlert);
|
||||
result.current.addAlert(duplicateAlert);
|
||||
result.current.addAlert(duplicateAlert);
|
||||
});
|
||||
|
||||
// Should allow duplicates
|
||||
expect(result.current.alerts).toHaveLength(3);
|
||||
result.current.alerts.forEach(alert => {
|
||||
expect(alert.message).toBe('Duplicate alert');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Alert Types', () => {
|
||||
it('should handle all alert types', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const alertTypes = [
|
||||
{ type: AlertType.Info, message: 'Info alert' },
|
||||
{ type: AlertType.Warning, message: 'Warning alert' },
|
||||
{ type: AlertType.Error, message: 'Error alert' },
|
||||
];
|
||||
|
||||
act(() => {
|
||||
alertTypes.forEach(alert => result.current.addAlert(alert));
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(3);
|
||||
expect(result.current.alerts[0].type).toBe(AlertType.Info);
|
||||
expect(result.current.alerts[1].type).toBe(AlertType.Warning);
|
||||
expect(result.current.alerts[2].type).toBe(AlertType.Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Progress Alerts', () => {
|
||||
it('should handle alerts with progress', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const progressAlert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Loading...',
|
||||
progress: { current: 25, total: 100 },
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(progressAlert);
|
||||
});
|
||||
|
||||
expect(result.current.alerts[0].progress).toEqual({ current: 25, total: 100 });
|
||||
});
|
||||
|
||||
it('should handle progress updates by replacing alerts', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
// Add initial progress alert
|
||||
act(() => {
|
||||
result.current.addAlert({
|
||||
type: AlertType.Info,
|
||||
message: 'Loading...',
|
||||
progress: { current: 25, total: 100 },
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.alerts[0].progress?.current).toBe(25);
|
||||
|
||||
// Clear and add updated progress
|
||||
act(() => {
|
||||
result.current.clearAlerts();
|
||||
result.current.addAlert({
|
||||
type: AlertType.Info,
|
||||
message: 'Loading...',
|
||||
progress: { current: 75, total: 100 },
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(1);
|
||||
expect(result.current.alerts[0].progress?.current).toBe(75);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Compact Button Alerts', () => {
|
||||
it('should handle alerts with compact functionality', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const mockOnCompact = vi.fn();
|
||||
const compactAlert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Context window full',
|
||||
showCompactButton: true,
|
||||
onCompact: mockOnCompact,
|
||||
compactIcon: <span>📦</span>,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(compactAlert);
|
||||
});
|
||||
|
||||
const alert = result.current.alerts[0];
|
||||
expect(alert.showCompactButton).toBe(true);
|
||||
expect(alert.onCompact).toBe(mockOnCompact);
|
||||
expect(alert.compactIcon).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Auto-show Alerts', () => {
|
||||
it('should handle autoShow property', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const autoShowAlert = {
|
||||
type: AlertType.Error,
|
||||
message: 'Critical error',
|
||||
autoShow: true,
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(autoShowAlert);
|
||||
});
|
||||
|
||||
expect(result.current.alerts[0].autoShow).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle alerts without autoShow property', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const regularAlert = {
|
||||
type: AlertType.Info,
|
||||
message: 'Regular alert',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert(regularAlert);
|
||||
});
|
||||
|
||||
expect(result.current.alerts[0].autoShow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty message', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert({
|
||||
type: AlertType.Info,
|
||||
message: '',
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.alerts).toHaveLength(1);
|
||||
expect(result.current.alerts[0].message).toBe('');
|
||||
});
|
||||
|
||||
it('should handle very long messages', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const longMessage = 'A'.repeat(1000);
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert({
|
||||
type: AlertType.Info,
|
||||
message: longMessage,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.alerts[0].message).toBe(longMessage);
|
||||
});
|
||||
|
||||
it('should handle special characters in messages', () => {
|
||||
const { result } = renderHook(() => useAlerts());
|
||||
|
||||
const specialMessage = '🚨 Alert with émojis and spëcial chars! @#$%^&*()';
|
||||
|
||||
act(() => {
|
||||
result.current.addAlert({
|
||||
type: AlertType.Warning,
|
||||
message: specialMessage,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.alerts[0].message).toBe(specialMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,8 @@ export interface Alert {
|
||||
current: number;
|
||||
total: number;
|
||||
};
|
||||
showSummarizeButton?: boolean;
|
||||
onSummarize?: () => void;
|
||||
summarizeIcon?: React.ReactNode;
|
||||
showCompactButton?: boolean;
|
||||
compactButtonDisabled?: boolean;
|
||||
onCompact?: () => void;
|
||||
compactIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user