adk-gateway 1.0.0

Multi-channel AI gateway for adk-rust agents — Telegram, Slack, WhatsApp, Discord, Matrix + control panel
/**
 * Unit tests for NotificationStack component — Task 2.5
 *
 * Tests verify:
 * - Renders notifications with correct styling per type
 * - Auto-dismisses success notifications after 3 seconds
 * - Error notifications require manual dismissal
 * - Warning notifications render with correct styling
 * - Dismiss button calls onDismiss with correct id
 * - Returns null when no notifications
 *
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, act } from '@testing-library/react';
import NotificationStack from '../NotificationStack';
import type { Notification } from '../NotificationStack';

describe('NotificationStack', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('renders nothing when notifications array is empty', () => {
    const { container } = render(
      <NotificationStack notifications={[]} onDismiss={vi.fn()} />
    );
    expect(container.firstChild).toBeNull();
  });

  it('renders success notification with green styling', () => {
    const notifications: Notification[] = [
      { id: '1', type: 'success', message: 'Agent created', persistent: false },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={vi.fn()} />);

    const alert = screen.getByRole('alert');
    expect(alert).toHaveClass('bg-green-50', 'border-green-300', 'text-green-800');
    expect(screen.getByText('Agent created')).toBeInTheDocument();
  });

  it('renders error notification with red styling', () => {
    const notifications: Notification[] = [
      { id: '1', type: 'error', message: 'Failed to save', persistent: true },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={vi.fn()} />);

    const alert = screen.getByRole('alert');
    expect(alert).toHaveClass('bg-red-50', 'border-red-300', 'text-red-800');
    expect(screen.getByText('Failed to save')).toBeInTheDocument();
  });

  it('renders warning notification with yellow styling', () => {
    const notifications: Notification[] = [
      { id: '1', type: 'warning', message: 'Cost threshold exceeded', persistent: false },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={vi.fn()} />);

    const alert = screen.getByRole('alert');
    expect(alert).toHaveClass('bg-yellow-50', 'border-yellow-300', 'text-yellow-800');
    expect(screen.getByText('Cost threshold exceeded')).toBeInTheDocument();
  });

  it('auto-dismisses success notifications after 3 seconds', () => {
    const onDismiss = vi.fn();
    const notifications: Notification[] = [
      { id: 'success-1', type: 'success', message: 'Done', persistent: false },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={onDismiss} />);

    expect(onDismiss).not.toHaveBeenCalled();

    act(() => {
      vi.advanceTimersByTime(3000);
    });

    expect(onDismiss).toHaveBeenCalledWith('success-1');
  });

  it('does not auto-dismiss error notifications', () => {
    const onDismiss = vi.fn();
    const notifications: Notification[] = [
      { id: 'error-1', type: 'error', message: 'Something broke', persistent: true },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={onDismiss} />);

    act(() => {
      vi.advanceTimersByTime(5000);
    });

    expect(onDismiss).not.toHaveBeenCalled();
  });

  it('calls onDismiss when dismiss button is clicked', () => {
    const onDismiss = vi.fn();
    const notifications: Notification[] = [
      { id: 'err-1', type: 'error', message: 'Error occurred', persistent: true },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={onDismiss} />);

    fireEvent.click(screen.getByLabelText('Dismiss notification'));

    expect(onDismiss).toHaveBeenCalledWith('err-1');
  });

  it('renders multiple notifications stacked', () => {
    const notifications: Notification[] = [
      { id: '1', type: 'success', message: 'Created', persistent: false },
      { id: '2', type: 'error', message: 'Failed', persistent: true },
      { id: '3', type: 'warning', message: 'Warning', persistent: false },
    ];

    render(<NotificationStack notifications={notifications} onDismiss={vi.fn()} />);

    expect(screen.getAllByRole('alert')).toHaveLength(3);
    expect(screen.getByText('Created')).toBeInTheDocument();
    expect(screen.getByText('Failed')).toBeInTheDocument();
    expect(screen.getByText('Warning')).toBeInTheDocument();
  });

  it('positions the stack fixed at top-right', () => {
    const notifications: Notification[] = [
      { id: '1', type: 'success', message: 'Test', persistent: false },
    ];

    const { container } = render(
      <NotificationStack notifications={notifications} onDismiss={vi.fn()} />
    );

    const stack = container.firstChild as HTMLElement;
    expect(stack).toHaveClass('fixed', 'top-4', 'right-4');
  });
});