import { describe, test, expect, beforeEach } from 'vitest' import { render, screen } from '@testing-library/react' import { SpreadUI } from './spread-ui' import { useWidgetStore } from '../stores/widget-store' import type { SpreadWidgetState } from './types' describe('SpreadUI', () => { beforeEach(() => { useWidgetStore.getState().clearWidgets() }) test('renders empty container when widget does not exist', () => { const { container } = render() // Box wrapper is rendered but Row returns null for missing widget expect(container.firstChild?.childNodes.length).toBe(0) }) test('renders with single spread data item', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 2.1, max: 2.23, suffix: '€' }], }) render() expect(screen.getByText('2.1')).toBeTruthy() expect(screen.getByText('-')).toBeTruthy() expect(screen.getByText('2.23')).toBeTruthy() expect(screen.getByText('€')).toBeTruthy() }) test('renders with multiple spread data items', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [ { min: 1.5, max: 2.5, prefix: '$' }, { min: 100, max: 200, suffix: '%' }, { min: 0, max: 10 }, ], }) render() expect(screen.getByText('$')).toBeTruthy() expect(screen.getByText('1.5')).toBeTruthy() expect(screen.getByText('2.5')).toBeTruthy() expect(screen.getByText('100')).toBeTruthy() expect(screen.getByText('200')).toBeTruthy() expect(screen.getByText('%')).toBeTruthy() expect(screen.getByText('0')).toBeTruthy() expect(screen.getByText('10')).toBeTruthy() }) test('renders prefix for each item', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [ { min: 1, max: 10, prefix: 'Range 1:' }, { min: 20, max: 30, prefix: 'Range 2:' }, ], }) render() expect(screen.getByText('Range 1:')).toBeTruthy() expect(screen.getByText('Range 2:')).toBeTruthy() }) test('renders separator between min and max values', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 5, max: 15 }], }) render() // Check for separator "-" const separators = screen.getAllByText('-') expect(separators.length).toBe(1) }) test('renders in correct order: Prefix, MinValue, Separator, MaxValue, Suffix', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 10, max: 20, prefix: '$', suffix: 'USD' }], }) const { container } = render() const items = container.querySelectorAll('.MuiTypography-root') expect(items.length).toBe(5) expect(items[0]?.textContent).toBe('$') expect(items[1]?.textContent).toBe('10') expect(items[2]?.textContent).toBe('-') expect(items[3]?.textContent).toBe('20') expect(items[4]?.textContent).toBe('USD') }) test('handles data without prefix', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 5, max: 15, suffix: 'km' }], }) render() expect(screen.getByText('5')).toBeTruthy() expect(screen.getByText('-')).toBeTruthy() expect(screen.getByText('15')).toBeTruthy() expect(screen.getByText('km')).toBeTruthy() }) test('handles data without suffix', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 5, max: 15, prefix: '$' }], }) render() expect(screen.getByText('$')).toBeTruthy() expect(screen.getByText('5')).toBeTruthy() expect(screen.getByText('-')).toBeTruthy() expect(screen.getByText('15')).toBeTruthy() }) test('handles data with only min and max values', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 5, max: 15 }], }) render() expect(screen.getByText('5')).toBeTruthy() expect(screen.getByText('-')).toBeTruthy() expect(screen.getByText('15')).toBeTruthy() }) test('handles empty data array', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [], }) const { container } = render() // Row component returns null for empty data, Box wrapper still rendered expect(container.firstChild?.childNodes.length).toBe(0) }) test('handles undefined data', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', }) const { container } = render() // Row component returns null for undefined data, Box wrapper still rendered expect(container.firstChild?.childNodes.length).toBe(0) }) test('renders with data containing colors', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [ { min: 1, max: 10, color: '#FF0000' }, { min: 20, max: 30, color: '#00FF00' }, ], }) render() expect(screen.getByText('1')).toBeTruthy() expect(screen.getByText('10')).toBeTruthy() expect(screen.getByText('20')).toBeTruthy() expect(screen.getByText('30')).toBeTruthy() }) test('renders with negative min value', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: -10, max: 5 }], }) render() expect(screen.getByText('-10')).toBeTruthy() expect(screen.getByText('-')).toBeTruthy() expect(screen.getByText('5')).toBeTruthy() }) test('renders with zero values', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 0, max: 0 }], }) render() const zeros = screen.getAllByText('0') expect(zeros.length).toBe(2) // min and max both are 0 }) test('renders with decimal values', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 2.1, max: 2.23, suffix: '€' }], }) render() expect(screen.getByText('2.1')).toBeTruthy() expect(screen.getByText('2.23')).toBeTruthy() expect(screen.getByText('€')).toBeTruthy() }) test('renders with large values', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 1000000, max: 2000000, prefix: '$' }], }) render() expect(screen.getByText('$')).toBeTruthy() expect(screen.getByText('1000000')).toBeTruthy() expect(screen.getByText('2000000')).toBeTruthy() }) test('updates when widget data changes', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 1, max: 10 }], }) const { rerender } = render() expect(screen.getByText('1')).toBeTruthy() expect(screen.getByText('10')).toBeTruthy() useWidgetStore.getState().setWidget('test-spread', { data: [{ min: 20, max: 30 }], }) rerender() expect(screen.queryByText('1')).toBeNull() expect(screen.queryByText('10')).toBeNull() expect(screen.getByText('20')).toBeTruthy() expect(screen.getByText('30')).toBeTruthy() }) test('handles widget removal gracefully', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 1, max: 10 }], }) const { container, rerender } = render() expect(screen.getByText('1')).toBeTruthy() useWidgetStore.getState().removeWidget('test-spread') rerender() // Box wrapper still rendered but Row returns null expect(container.firstChild?.childNodes.length).toBe(0) }) test('renders multiple rows for multiple data items', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [ { min: 1, max: 10, prefix: 'A' }, { min: 20, max: 30, prefix: 'B' }, { min: 40, max: 50, prefix: 'C' }, ], }) const { container } = render() // Each data item should create a row const rows = container.querySelectorAll('[class*="MuiBox-root"]') expect(rows.length).toBeGreaterThan(0) }) test('renders with series configuration', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 10, max: 100, suffix: '€' }], series: [{ name: 'Revenue', color: '#FF5733' }], }) render() // Series should display the first letter of the name expect(screen.getByText('R')).toBeTruthy() expect(screen.getByText('10')).toBeTruthy() expect(screen.getByText('100')).toBeTruthy() expect(screen.getByText('€')).toBeTruthy() }) test('renders without series when not provided', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 5, max: 25 }], }) const { container } = render() // Should not have any Avatar elements (series) const avatars = container.querySelectorAll('.MuiAvatar-root') expect(avatars.length).toBe(0) }) test('renders series with default color when color not provided', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [{ min: 0, max: 50 }], series: [{ name: 'Test' }], }) render() // Series should display the first letter expect(screen.getByText('T')).toBeTruthy() }) test('renders with array of series matching data indices', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [ { min: 10, max: 100 }, { min: 20, max: 200 }, { min: 30, max: 300 }, ], series: [ { name: 'Alpha', color: '#FF0000' }, { name: 'Beta', color: '#00FF00' }, { name: 'Gamma', color: '#0000FF' }, ], }) render() // Each series should display its first letter expect(screen.getByText('A')).toBeTruthy() expect(screen.getByText('B')).toBeTruthy() expect(screen.getByText('G')).toBeTruthy() }) test('renders partial series array - only shows series for available indices', () => { useWidgetStore.getState().setWidget('test-spread', { type: 'spread', data: [ { min: 10, max: 100 }, { min: 20, max: 200 }, { min: 30, max: 300 }, ], series: [{ name: 'Only First', color: '#FF0000' }], }) const { container } = render() // Only one series avatar should be rendered const avatars = container.querySelectorAll('.MuiAvatar-root') expect(avatars.length).toBe(1) expect(screen.getByText('O')).toBeTruthy() }) })