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()
})
})