From cec24b5dcfe5e2daa0f1b5de8db1848008b66817 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Sun, 28 Dec 2025 16:31:55 -0600 Subject: [PATCH] MAESTRO: Add EmptyState component for Usage Dashboard - Create EmptyState component with theme-aware styling - Features BarChart3 icon with decorative SVG bars - Supports customizable title and message props - Default message: "No usage data yet. Start using Maestro to see your stats!" - Update UsageDashboardModal to use new component - Add comprehensive test suite (16 tests) --- .../UsageDashboard/EmptyState.test.tsx | 181 ++++++++++++++++++ .../components/UsageDashboard/EmptyState.tsx | 97 ++++++++++ .../UsageDashboard/UsageDashboardModal.tsx | 19 +- 3 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 src/__tests__/renderer/components/UsageDashboard/EmptyState.test.tsx create mode 100644 src/renderer/components/UsageDashboard/EmptyState.tsx diff --git a/src/__tests__/renderer/components/UsageDashboard/EmptyState.test.tsx b/src/__tests__/renderer/components/UsageDashboard/EmptyState.test.tsx new file mode 100644 index 00000000..40868a77 --- /dev/null +++ b/src/__tests__/renderer/components/UsageDashboard/EmptyState.test.tsx @@ -0,0 +1,181 @@ +/** + * Tests for EmptyState component + * + * Verifies: + * - Renders empty state container with correct test ID + * - Displays chart icon illustration + * - Shows default title and message + * - Supports custom title and message + * - Applies theme colors properly + * - Theme-aware styling for all elements + */ + +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { EmptyState } from '../../../../renderer/components/UsageDashboard/EmptyState'; +import { THEMES } from '../../../../shared/themes'; + +// Test themes +const darkTheme = THEMES['dracula']; +const lightTheme = THEMES['solarized-light']; + +describe('EmptyState', () => { + describe('Rendering', () => { + it('renders the empty state container with correct test ID', () => { + render(); + + expect(screen.getByTestId('usage-dashboard-empty')).toBeInTheDocument(); + }); + + it('renders the chart icon illustration', () => { + render(); + + // The container should have the icon (BarChart3 renders as svg) + const container = screen.getByTestId('usage-dashboard-empty'); + const svgs = container.querySelectorAll('svg'); + + // Should have at least 2 svgs: main icon and decorative bars + expect(svgs.length).toBeGreaterThanOrEqual(2); + }); + + it('displays the default title', () => { + render(); + + expect(screen.getByText('No usage data yet')).toBeInTheDocument(); + }); + + it('displays the default message', () => { + render(); + + expect(screen.getByText('Start using Maestro to see your stats!')).toBeInTheDocument(); + }); + }); + + describe('Custom Content', () => { + it('supports custom title', () => { + render( + + ); + + expect(screen.getByText('No data for this period')).toBeInTheDocument(); + expect(screen.queryByText('No usage data yet')).not.toBeInTheDocument(); + }); + + it('supports custom message', () => { + render( + + ); + + expect(screen.getByText('Try selecting a different time range.')).toBeInTheDocument(); + expect(screen.queryByText('Start using Maestro to see your stats!')).not.toBeInTheDocument(); + }); + + it('supports both custom title and message', () => { + render( + + ); + + expect(screen.getByText('Custom Title')).toBeInTheDocument(); + expect(screen.getByText('Custom message for testing.')).toBeInTheDocument(); + }); + }); + + describe('Theme Styling', () => { + it('applies theme textDim color to container', () => { + render(); + + const container = screen.getByTestId('usage-dashboard-empty'); + expect(container).toHaveStyle({ color: darkTheme.colors.textDim }); + }); + + it('applies theme textMain color to title', () => { + render(); + + const title = screen.getByText('No usage data yet'); + expect(title).toHaveStyle({ color: darkTheme.colors.textMain }); + }); + + it('works with light theme', () => { + render(); + + const container = screen.getByTestId('usage-dashboard-empty'); + expect(container).toHaveStyle({ color: lightTheme.colors.textDim }); + + const title = screen.getByText('No usage data yet'); + expect(title).toHaveStyle({ color: lightTheme.colors.textMain }); + }); + }); + + describe('Layout', () => { + it('uses flexbox centering layout', () => { + render(); + + const container = screen.getByTestId('usage-dashboard-empty'); + expect(container).toHaveClass('flex'); + expect(container).toHaveClass('flex-col'); + expect(container).toHaveClass('items-center'); + expect(container).toHaveClass('justify-center'); + }); + + it('has gap between icon and text', () => { + render(); + + const container = screen.getByTestId('usage-dashboard-empty'); + expect(container).toHaveClass('gap-4'); + }); + + it('has full height container', () => { + render(); + + const container = screen.getByTestId('usage-dashboard-empty'); + expect(container).toHaveClass('h-full'); + }); + }); + + describe('Icon Styling', () => { + it('renders decorative svg with theme-aware fills', () => { + render(); + + const container = screen.getByTestId('usage-dashboard-empty'); + const rects = container.querySelectorAll('rect'); + + // The decorative bars should have rect elements + expect(rects.length).toBeGreaterThan(0); + + // Check that rects use the theme color + rects.forEach((rect) => { + expect(rect.getAttribute('fill')).toBe(darkTheme.colors.textDim); + }); + }); + }); + + describe('Accessibility', () => { + it('has a data-testid for testing', () => { + render(); + + expect(screen.getByTestId('usage-dashboard-empty')).toBeInTheDocument(); + }); + + it('text is visible and readable', () => { + render(); + + const title = screen.getByText('No usage data yet'); + const message = screen.getByText('Start using Maestro to see your stats!'); + + // Text should be in the document and visible + expect(title).toBeVisible(); + expect(message).toBeVisible(); + }); + }); +}); diff --git a/src/renderer/components/UsageDashboard/EmptyState.tsx b/src/renderer/components/UsageDashboard/EmptyState.tsx new file mode 100644 index 00000000..25dea65b --- /dev/null +++ b/src/renderer/components/UsageDashboard/EmptyState.tsx @@ -0,0 +1,97 @@ +/** + * EmptyState + * + * Displays a friendly empty state message when no usage data exists. + * Used in the Usage Dashboard to indicate that the user should start + * using Maestro to generate stats. + * + * Features: + * - Theme-aware styling with inline styles + * - Subtle chart illustration/icon + * - Friendly, encouraging message + * - Reusable component with customizable message + */ + +import React from 'react'; +import { BarChart3 } from 'lucide-react'; +import type { Theme } from '../../types'; + +interface EmptyStateProps { + /** Current theme for styling */ + theme: Theme; + /** Optional custom title (default: "No usage data yet") */ + title?: string; + /** Optional custom message (default: "Start using Maestro to see your stats!") */ + message?: string; +} + +export function EmptyState({ + theme, + title = 'No usage data yet', + message = 'Start using Maestro to see your stats!', +}: EmptyStateProps) { + return ( +
+ {/* Subtle chart illustration */} +
+ {/* Main chart icon */} + + + {/* Decorative subtle bars for visual interest */} + + + + + +
+ + {/* Message text */} +
+

+ {title} +

+

+ {message} +

+
+
+ ); +} + +export default EmptyState; diff --git a/src/renderer/components/UsageDashboard/UsageDashboardModal.tsx b/src/renderer/components/UsageDashboard/UsageDashboardModal.tsx index 0384313b..999ddd19 100644 --- a/src/renderer/components/UsageDashboard/UsageDashboardModal.tsx +++ b/src/renderer/components/UsageDashboard/UsageDashboardModal.tsx @@ -21,6 +21,7 @@ import { AgentComparisonChart } from './AgentComparisonChart'; import { SourceDistributionChart } from './SourceDistributionChart'; import { DurationTrendsChart } from './DurationTrendsChart'; import { AutoRunStats } from './AutoRunStats'; +import { EmptyState } from './EmptyState'; import type { Theme } from '../../types'; import { useLayerStack } from '../../contexts/LayerStackContext'; import { MODAL_PRIORITIES } from '../../constants/modalPriorities'; @@ -377,22 +378,8 @@ export function UsageDashboardModal({ ) : !data || (data.totalQueries === 0 && data.bySource.user === 0 && data.bySource.auto === 0) ? ( - /* Empty State */ -
- -
-

- No usage data yet -

-

- Start using Maestro to see your stats! -

-
-
+ /* Empty State Component */ + ) : (
{/* View-specific content based on viewMode */}