import { ComponentProps, ReactNode } from 'react'; import { render, screen } from '@testing-library/react'; import { ResourceStatBlock, ResourceStatBlockStatus, } from './ResourceStatBlock'; type ValueProps = ComponentProps; type ValueSize = NonNullable; type ValueAlign = NonNullable; interface RenderOptions { status?: ResourceStatBlockStatus; labelIcon?: ReactNode; value?: ReactNode; valueSuffix?: ReactNode; valueDot?: boolean; valueSize?: ValueSize; valueAlign?: ValueAlign; meta?: ReactNode; } function renderComponent({ status, labelIcon, value = 'Synced', valueSuffix, valueDot, valueSize, valueAlign, meta, }: RenderOptions = {}) { return render( Sync Status {value} {meta && {meta}} ); } describe('ResourceStatBlock', () => { it('should render the label and value', () => { renderComponent(); expect(screen.getByText('Sync Status')).toBeVisible(); expect(screen.getByText('Synced')).toBeVisible(); }); it('should render the icon when provided to Label', () => { renderComponent({ labelIcon: i }); expect(screen.getByTestId('stat-icon')).toBeInTheDocument(); }); it('should render the suffix when provided to Value', () => { renderComponent({ valueSuffix: 'healthy' }); expect(screen.getByText('healthy')).toBeVisible(); }); it('should omit the suffix when not provided', () => { renderComponent(); expect(screen.queryByText('healthy')).not.toBeInTheDocument(); }); it('should render the meta line when provided', () => { renderComponent({ meta: 'Last reconciled 2 min ago' }); expect(screen.getByText('Last reconciled 2 min ago')).toBeVisible(); }); it('should omit the meta line when not provided', () => { renderComponent(); expect( screen.queryByText('Last reconciled 2 min ago') ).not.toBeInTheDocument(); }); describe('dot', () => { it('should not render a dot by default', () => { renderComponent(); expect(screen.queryByTestId('stat-block-dot')).not.toBeInTheDocument(); }); it('should render a dot inheriting the container status when dot is true', () => { renderComponent({ valueDot: true, status: 'success' }); const wrapper = screen.getByTestId('stat-block-dot'); expect(wrapper.firstElementChild).toHaveClass('bg-success-7'); }); }); describe('status variants', () => { const containerCases: Array<[ResourceStatBlockStatus, string]> = [ ['success', 'border-green-4'], ['danger', 'border-error-4'], ['warning', 'border-warning-4'], ['pending', 'border-blue-4'], ['muted', 'border-gray-4'], ]; it.each(containerCases)( 'should apply the %s container styling', (status, expectedClass) => { renderComponent({ status }); expect(screen.getByTestId('stat-block')).toHaveClass(expectedClass); } ); const valueCases: Array<[ResourceStatBlockStatus, string]> = [ ['success', 'text-green-8'], ['danger', 'text-error-8'], ['warning', 'text-warning-8'], ['pending', 'text-blue-8'], ['muted', 'text-graphite-700'], ]; it.each(valueCases)( 'should apply the %s value text color', (status, expectedClass) => { renderComponent({ status }); expect(screen.getByText('Synced')).toHaveClass(expectedClass); } ); it('should default to muted styling when status is omitted', () => { renderComponent(); expect(screen.getByTestId('stat-block')).toHaveClass('border-gray-4'); expect(screen.getByText('Synced')).toHaveClass('text-graphite-700'); }); }); describe('Value align', () => { const cases: Array<[ValueAlign, string]> = [ ['start', 'justify-start'], ['center', 'justify-center'], ['end', 'justify-end'], ]; it.each(cases)( 'should align the value row to %s', (align, expectedClass) => { renderComponent({ valueAlign: align }); expect(screen.getByText('Synced').parentElement).toHaveClass( expectedClass ); } ); it('should default to start alignment', () => { renderComponent(); expect(screen.getByText('Synced').parentElement).toHaveClass( 'justify-start' ); }); }); describe('Value size', () => { const cases: Array<[ValueSize, string]> = [ ['xs', 'text-xs'], ['sm', 'text-sm'], ['base', 'text-base'], ['lg', 'text-lg'], ]; it.each(cases)( 'should size the value text as %s', (size, expectedClass) => { renderComponent({ valueSize: size }); expect(screen.getByText('Synced')).toHaveClass(expectedClass); } ); it('should default to xs sizing', () => { renderComponent(); expect(screen.getByText('Synced')).toHaveClass('text-xs'); }); }); });