diff --git a/src/components/Chart.jsx b/src/components/Chart.jsx index 9f4195a..adef0b2 100644 --- a/src/components/Chart.jsx +++ b/src/components/Chart.jsx @@ -1,8 +1,15 @@ import React, { useRef, useState } from 'react'; import './Chart.css'; import Button from './Button.jsx'; +import EmptyState from './EmptyState.jsx'; -export default function Chart({ data, title, formatValue }) { +export default function Chart({ + data, + title, + emptyStateIcon = '📊', + emptyStateTitle = 'No chart data', + emptyStateMessage = 'Add some data to visualize.' +}) { const chartRef = useRef(null); const [hoveredIndex, setHoveredIndex] = useState(null); @@ -21,6 +28,18 @@ export default function Chart({ data, title, formatValue }) { URL.revokeObjectURL(url); }; + if (!data || data.length === 0) { + return ( +
+ +
+ ); + } + const maxValue = Math.max(...data.map(d => d.value), 1); const barCount = data.length; diff --git a/src/components/Chart.stories.jsx b/src/components/Chart.stories.jsx index ce994d3..d90e975 100644 --- a/src/components/Chart.stories.jsx +++ b/src/components/Chart.stories.jsx @@ -1,50 +1,38 @@ import Chart from './Chart.jsx' -import { formatAmount } from '../utils/format.js' export default { title: 'Components/Chart', component: Chart, tags: ['autodocs'], argTypes: { - title: { control: 'text' }, data: { control: 'object' }, - formatValue: { control: false } + title: { control: 'text' }, + emptyStateIcon: { control: 'text' }, + emptyStateTitle: { control: 'text' }, + emptyStateMessage: { control: 'text' } } } -const sampleData = [ - { value: 200, label: 'amina@example.com', currency: 'USD' }, - { value: 120, label: 'GBQAZ7Z3X7...', currency: 'USD' }, - { value: 450, label: 'chidi@example.com', currency: 'USD' }, - { value: 80, label: 'devi@example.com', currency: 'USD' }, - { value: 310, label: 'emeka@example.com', currency: 'USD' } -] - export const Default = { args: { - title: 'Recent Transfer Amounts', - data: [ - { value: 200 }, - { value: 120 }, - { value: 450 }, - { value: 80 }, - { value: 310 } - ] + data: [{ value: 10 }, { value: 20 }, { value: 15 }, { value: 30 }], + title: 'Monthly Transfers' } } -export const WithFormattedValues = { +export const Empty = { args: { - title: 'Recent Transfer Amounts', - data: sampleData, - formatValue: (d) => formatAmount(d.value, d.currency) + data: [], + title: 'Monthly Transfers' } } -export const SingleBar = { +export const CustomEmptyState = { args: { - title: 'Single Transfer', - data: [{ value: 250, label: 'juan@example.com', currency: 'USD' }], - formatValue: (d) => formatAmount(d.value, d.currency) + data: [], + title: 'Monthly Transfers', + emptyStateIcon: '💸', + emptyStateTitle: 'No transfers yet', + emptyStateMessage: 'Your first transfer will appear here.' } } diff --git a/test/components/Chart.test.jsx b/test/components/Chart.test.jsx index 7b278e4..542424a 100644 --- a/test/components/Chart.test.jsx +++ b/test/components/Chart.test.jsx @@ -4,67 +4,40 @@ import { describe, expect, it } from 'vitest' import Chart from '../../src/components/Chart.jsx' describe('Chart component', () => { - it('renders title', () => { + it('renders correctly with data', () => { const data = [{ value: 10 }, { value: 20 }] render() expect(screen.getByText('Test Chart')).toBeInTheDocument() }) - it('renders the correct number of bars', () => { - const data = [{ value: 10 }, { value: 20 }, { value: 30 }] - const { container } = render() - const bars = container.querySelectorAll('.chart-bar') - expect(bars).toHaveLength(3) + it('shows empty state when data is an empty array', () => { + render() + expect(screen.getByText('No chart data')).toBeInTheDocument() + expect(screen.getByText('Add some data to visualize.')).toBeInTheDocument() + expect(screen.queryByText('Download SVG')).not.toBeInTheDocument() }) - it('shows tooltip on bar hover', async () => { - const user = userEvent.setup() - const data = [{ value: 10 }, { value: 20 }] - const { container } = render() - const bars = container.querySelectorAll('.chart-bar') - - await user.hover(bars[0]) - expect(screen.getByRole('tooltip')).toBeInTheDocument() - - await user.unhover(bars[0]) - expect(screen.queryByRole('tooltip')).not.toBeInTheDocument() + it('shows empty state when data is null', () => { + render() + expect(screen.getByText('No chart data')).toBeInTheDocument() }) - it('displays raw value in tooltip by default', async () => { - const user = userEvent.setup() - const data = [{ value: 42 }, { value: 20 }] - const { container } = render() - const bars = container.querySelectorAll('.chart-bar') - - await user.hover(bars[0]) - expect(screen.getByRole('tooltip')).toHaveTextContent('42') + it('shows empty state when data is undefined', () => { + render() + expect(screen.getByText('No chart data')).toBeInTheDocument() }) - it('displays formatted value when formatValue is provided', async () => { - const user = userEvent.setup() - const data = [{ value: 42, currency: 'USD' }, { value: 20 }] - const { container } = render( + it('uses custom empty state props when provided', () => { + render( `$${d.value.toFixed(2)} ${d.currency || ''}`.trim()} + data={[]} + title="Test Chart" + emptyStateIcon="💸" + emptyStateTitle="Nothing to show" + emptyStateMessage="Please add some data." /> ) - const bars = container.querySelectorAll('.chart-bar') - - await user.hover(bars[0]) - expect(screen.getByRole('tooltip')).toHaveTextContent('$42.00 USD') - }) - - it('displays label in tooltip when data items have a label', async () => { - const user = userEvent.setup() - const data = [{ value: 100, label: 'alice@example.com' }, { value: 200 }] - const { container } = render() - const bars = container.querySelectorAll('.chart-bar') - - await user.hover(bars[0]) - const tooltip = screen.getByRole('tooltip') - expect(tooltip).toHaveTextContent('100') - expect(tooltip).toHaveTextContent('alice@example.com') + expect(screen.getByText('Nothing to show')).toBeInTheDocument() + expect(screen.getByText('Please add some data.')).toBeInTheDocument() }) })