datajunction-ui
Version:
DataJunction UI
1,411 lines (1,189 loc) • 44.7 kB
JSX
import { render, screen, fireEvent } from '@testing-library/react';
import { SelectionPanel } from '../SelectionPanel';
import React from 'react';
const mockMetrics = [
'default.num_repair_orders',
'default.avg_repair_price',
'default.total_repair_cost',
'sales.revenue',
'sales.order_count',
'inventory.stock_level',
];
const mockDimensions = [
{
name: 'default.date_dim.dateint',
type: 'timestamp',
path: ['default.date_dim'],
},
{
name: 'default.date_dim.month',
type: 'int',
path: ['default.date_dim'],
},
{
name: 'default.date_dim.year',
type: 'int',
path: ['default.date_dim'],
},
{
name: 'default.customer.country',
type: 'string',
path: ['default.customer'],
},
];
const defaultProps = {
metrics: mockMetrics,
selectedMetrics: [],
onMetricsChange: vi.fn(),
dimensions: mockDimensions,
selectedDimensions: [],
onDimensionsChange: vi.fn(),
loading: false,
};
describe('SelectionPanel', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('Metrics Section', () => {
it('renders metrics section header', () => {
render(<SelectionPanel {...defaultProps} />);
expect(screen.getByText('Metrics')).toBeInTheDocument();
});
it('displays selection count', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
expect(screen.getByText('1 selected')).toBeInTheDocument();
});
it('groups metrics by namespace', () => {
render(<SelectionPanel {...defaultProps} />);
expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('sales')).toBeInTheDocument();
expect(screen.getByText('inventory')).toBeInTheDocument();
});
it('shows metric count per namespace', () => {
render(<SelectionPanel {...defaultProps} />);
// default has 3 metrics
expect(screen.getByText('3')).toBeInTheDocument();
});
it('expands namespace when clicked', () => {
render(<SelectionPanel {...defaultProps} />);
const defaultNamespace = screen.getByText('default');
fireEvent.click(defaultNamespace);
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
});
it('collapses namespace when clicked again', () => {
render(<SelectionPanel {...defaultProps} />);
const defaultNamespace = screen.getByText('default');
fireEvent.click(defaultNamespace);
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
fireEvent.click(defaultNamespace);
expect(screen.queryByText('num_repair_orders')).not.toBeInTheDocument();
});
it('calls onMetricsChange when metric is selected', () => {
const onMetricsChange = vi.fn();
render(
<SelectionPanel {...defaultProps} onMetricsChange={onMetricsChange} />,
);
// Expand namespace first
fireEvent.click(screen.getByText('default'));
// Click checkbox
const checkbox = screen.getByRole('checkbox', {
name: /num_repair_orders/i,
});
fireEvent.click(checkbox);
expect(onMetricsChange).toHaveBeenCalledWith([
'default.num_repair_orders',
]);
});
it('removes metric when unchecked', () => {
const onMetricsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
onMetricsChange={onMetricsChange}
/>,
);
fireEvent.click(screen.getByText('default'));
const checkbox = screen.getByRole('checkbox', {
name: /num_repair_orders/i,
});
fireEvent.click(checkbox);
expect(onMetricsChange).toHaveBeenCalledWith([]);
});
});
describe('Metrics Search', () => {
it('renders search input', () => {
render(<SelectionPanel {...defaultProps} />);
expect(
screen.getByPlaceholderText('Search metrics...'),
).toBeInTheDocument();
});
it('filters metrics by search term', () => {
render(<SelectionPanel {...defaultProps} />);
const searchInput = screen.getByPlaceholderText('Search metrics...');
fireEvent.change(searchInput, { target: { value: 'repair' } });
// Should auto-expand and show matching metrics
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
});
it('filters out non-matching metrics', () => {
render(<SelectionPanel {...defaultProps} />);
const searchInput = screen.getByPlaceholderText('Search metrics...');
fireEvent.change(searchInput, { target: { value: 'revenue' } });
// Only sales.revenue should match
expect(screen.getByText('revenue')).toBeInTheDocument();
expect(screen.queryByText('num_repair_orders')).not.toBeInTheDocument();
});
it('shows no results message when no metrics match', () => {
render(<SelectionPanel {...defaultProps} />);
const searchInput = screen.getByPlaceholderText('Search metrics...');
fireEvent.change(searchInput, { target: { value: 'nonexistent' } });
expect(
screen.getByText('No metrics match your search'),
).toBeInTheDocument();
});
it('prioritizes prefix matches in search results', () => {
const metricsWithSimilarNames = [
'default.total_orders',
'default.orders_total',
'default.order_count',
];
render(
<SelectionPanel {...defaultProps} metrics={metricsWithSimilarNames} />,
);
const searchInput = screen.getByPlaceholderText('Search metrics...');
fireEvent.change(searchInput, { target: { value: 'order' } });
// order_count should appear (prefix match on short name)
// orders_total should appear (contains 'order')
const items = screen.getAllByRole('checkbox');
expect(items.length).toBeGreaterThan(0);
});
});
describe('Select All / Clear Actions', () => {
it('shows Select all and Clear buttons when namespace is expanded', () => {
render(<SelectionPanel {...defaultProps} />);
fireEvent.click(screen.getByText('default'));
expect(screen.getByText('Select all')).toBeInTheDocument();
// Check for namespace-level Clear button (inside namespace-actions)
// Both buttons have select-all-btn class, Clear is the second one
const namespaceButtons = document.querySelectorAll(
'.namespace-actions .select-all-btn',
);
expect(namespaceButtons.length).toBe(2);
expect(namespaceButtons[1].textContent).toBe('Clear');
});
it('selects all metrics in namespace when Select all is clicked', () => {
const onMetricsChange = vi.fn();
render(
<SelectionPanel {...defaultProps} onMetricsChange={onMetricsChange} />,
);
fireEvent.click(screen.getByText('default'));
fireEvent.click(screen.getByText('Select all'));
expect(onMetricsChange).toHaveBeenCalledWith([
'default.num_repair_orders',
'default.avg_repair_price',
'default.total_repair_cost',
]);
});
it('clears all metrics in namespace when namespace Clear is clicked', () => {
const onMetricsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={[
'default.num_repair_orders',
'default.avg_repair_price',
]}
onMetricsChange={onMetricsChange}
/>,
);
fireEvent.click(screen.getByText('default'));
// Click the namespace-level Clear button (second button in namespace-actions)
const namespaceButtons = document.querySelectorAll(
'.namespace-actions .select-all-btn',
);
fireEvent.click(namespaceButtons[1]); // Clear is the second button
expect(onMetricsChange).toHaveBeenCalledWith([]);
});
});
describe('Dimensions Section', () => {
it('renders dimensions section header', () => {
render(<SelectionPanel {...defaultProps} />);
expect(screen.getByText('Dimensions')).toBeInTheDocument();
});
it('shows hint when no metrics selected', () => {
render(<SelectionPanel {...defaultProps} selectedMetrics={[]} />);
expect(
screen.getByText('Select metrics to see available dimensions'),
).toBeInTheDocument();
});
it('shows loading state while fetching dimensions', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
loading={true}
/>,
);
expect(screen.getByText('Loading dimensions...')).toBeInTheDocument();
});
it('displays dimensions when metrics are selected', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
expect(screen.getByText('date_dim.dateint')).toBeInTheDocument();
expect(screen.getByText('date_dim.month')).toBeInTheDocument();
});
it('calls onDimensionsChange when dimension is selected', () => {
const onDimensionsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
onDimensionsChange={onDimensionsChange}
/>,
);
const checkbox = screen.getByRole('checkbox', { name: /dateint/i });
fireEvent.click(checkbox);
expect(onDimensionsChange).toHaveBeenCalledWith([
'default.date_dim.dateint',
]);
});
it('deduplicates dimensions with same name', () => {
const duplicateDimensions = [
{ name: 'default.date_dim.month', path: ['path1'] },
{ name: 'default.date_dim.month', path: ['short'] },
];
render(
<SelectionPanel
{...defaultProps}
dimensions={duplicateDimensions}
selectedMetrics={['default.test']}
/>,
);
// Should only show one checkbox for month
const monthCheckboxes = screen.getAllByRole('checkbox', {
name: /month/i,
});
expect(monthCheckboxes.length).toBe(1);
});
it('shows dimension display name (last 2 segments)', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
// Should show 'date_dim.dateint' not full path
expect(screen.getByText('date_dim.dateint')).toBeInTheDocument();
});
});
describe('Dimensions Search', () => {
it('filters dimensions by search term', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
const searchInput = screen.getByPlaceholderText('Search dimensions...');
fireEvent.change(searchInput, { target: { value: 'month' } });
expect(screen.getByText('date_dim.month')).toBeInTheDocument();
expect(screen.queryByText('date_dim.year')).not.toBeInTheDocument();
});
it('shows no results message when no dimensions match', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
const searchInput = screen.getByPlaceholderText('Search dimensions...');
fireEvent.change(searchInput, { target: { value: 'nonexistent' } });
expect(
screen.getByText('No dimensions match your search'),
).toBeInTheDocument();
});
});
describe('Selected State Display', () => {
it('shows selected badge in namespace when metrics are selected', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={[
'default.num_repair_orders',
'default.avg_repair_price',
]}
/>,
);
// Should show '2' in the selected badge
const selectedBadge = document.querySelector('.selected-badge');
expect(selectedBadge).toBeInTheDocument();
expect(selectedBadge).toHaveTextContent('2');
});
it('shows checked state for selected metrics', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
fireEvent.click(screen.getByText('default'));
const checkbox = screen.getByRole('checkbox', {
name: /num_repair_orders/i,
});
expect(checkbox).toBeChecked();
});
it('shows checked state for selected dimensions', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={['default.date_dim.dateint']}
/>,
);
const checkbox = screen.getByRole('checkbox', { name: /dateint/i });
expect(checkbox).toBeChecked();
});
});
describe('Cube Preset Loading', () => {
const cubeProps = {
...defaultProps,
cubes: [
{ name: 'default.test_cube', display_name: 'Test Cube' },
{ name: 'sales.revenue_cube', display_name: 'Revenue Cube' },
],
onLoadCubePreset: vi.fn(),
};
it('shows Load from Cube button when cubes are available', () => {
render(<SelectionPanel {...cubeProps} />);
expect(screen.getByText('Load from Cube')).toBeInTheDocument();
});
it('opens dropdown when Load from Cube button is clicked', () => {
render(<SelectionPanel {...cubeProps} />);
fireEvent.click(screen.getByText('Load from Cube'));
expect(
screen.getByPlaceholderText('Search cubes...'),
).toBeInTheDocument();
});
it('displays cube options in dropdown', () => {
render(<SelectionPanel {...cubeProps} />);
fireEvent.click(screen.getByText('Load from Cube'));
expect(screen.getByText('Test Cube')).toBeInTheDocument();
expect(screen.getByText('Revenue Cube')).toBeInTheDocument();
});
it('filters cubes by search term', () => {
render(<SelectionPanel {...cubeProps} />);
fireEvent.click(screen.getByText('Load from Cube'));
const searchInput = screen.getByPlaceholderText('Search cubes...');
fireEvent.change(searchInput, { target: { value: 'Revenue' } });
expect(screen.getByText('Revenue Cube')).toBeInTheDocument();
expect(screen.queryByText('Test Cube')).not.toBeInTheDocument();
});
it('calls onLoadCubePreset when a cube is selected', () => {
const onLoadCubePreset = vi.fn();
render(
<SelectionPanel {...cubeProps} onLoadCubePreset={onLoadCubePreset} />,
);
fireEvent.click(screen.getByText('Load from Cube'));
fireEvent.click(screen.getByText('Test Cube'));
expect(onLoadCubePreset).toHaveBeenCalledWith('default.test_cube');
});
it('shows loaded cube name in button when cube is loaded', () => {
render(
<SelectionPanel {...cubeProps} loadedCubeName="default.test_cube" />,
);
// Should show the cube display name or short name
expect(screen.getByText('Test Cube')).toBeInTheDocument();
});
it('shows "No cubes match your search" when search has no results', () => {
render(<SelectionPanel {...cubeProps} />);
fireEvent.click(screen.getByText('Load from Cube'));
const searchInput = screen.getByPlaceholderText('Search cubes...');
fireEvent.change(searchInput, { target: { value: 'nonexistent' } });
expect(
screen.getByText('No cubes match your search'),
).toBeInTheDocument();
});
it('closes dropdown when clicking outside', () => {
render(<SelectionPanel {...cubeProps} />);
fireEvent.click(screen.getByText('Load from Cube'));
expect(
screen.getByPlaceholderText('Search cubes...'),
).toBeInTheDocument();
// Simulate clicking outside
fireEvent.mouseDown(document.body);
expect(
screen.queryByPlaceholderText('Search cubes...'),
).not.toBeInTheDocument();
});
});
describe('Selected Metrics Chips', () => {
it('displays selected metrics as chips', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
});
it('removes metric when chip remove button is clicked', () => {
const onMetricsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={[
'default.num_repair_orders',
'default.avg_repair_price',
]}
onMetricsChange={onMetricsChange}
/>,
);
// Find the remove button for num_repair_orders chip
const removeBtn = screen.getByTitle('Remove num_repair_orders');
fireEvent.click(removeBtn);
expect(onMetricsChange).toHaveBeenCalledWith([
'default.avg_repair_price',
]);
});
it('shows "Show all" button when many metrics are selected', () => {
const manyMetrics = Array.from(
{ length: 12 },
(_, i) => `default.metric_${i}`,
);
render(
<SelectionPanel
{...defaultProps}
metrics={manyMetrics}
selectedMetrics={manyMetrics}
/>,
);
expect(screen.getByText('Show all')).toBeInTheDocument();
});
it('toggles chips expansion when Show all/Show less is clicked', () => {
const manyMetrics = Array.from(
{ length: 12 },
(_, i) => `default.metric_${i}`,
);
render(
<SelectionPanel
{...defaultProps}
metrics={manyMetrics}
selectedMetrics={manyMetrics}
/>,
);
// Click to expand
const expandBtn = screen.getByText('Show all');
fireEvent.click(expandBtn);
// Should now show "Show less"
expect(screen.getByText('Show less')).toBeInTheDocument();
// Click to collapse
fireEvent.click(screen.getByText('Show less'));
// Should show "Show all" again
expect(screen.getByText('Show all')).toBeInTheDocument();
});
});
describe('Selected Dimensions Chips', () => {
it('displays selected dimensions as chips', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={['default.date_dim.dateint']}
/>,
);
// Check for chip by looking for the chip container with the dimension display name
const chipElements = screen.getAllByText('date_dim.dateint');
// Should have at least one chip (and possibly one in the list)
expect(chipElements.length).toBeGreaterThanOrEqual(1);
// The chip should have the dimension-chip class
expect(document.querySelector('.dimension-chip')).toBeInTheDocument();
});
it('removes dimension when chip remove button is clicked', () => {
const onDimensionsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={[
'default.date_dim.dateint',
'default.date_dim.month',
]}
onDimensionsChange={onDimensionsChange}
/>,
);
// Find the remove button for dateint chip
const removeBtn = screen.getByTitle('Remove date_dim.dateint');
fireEvent.click(removeBtn);
expect(onDimensionsChange).toHaveBeenCalledWith([
'default.date_dim.month',
]);
});
});
describe('Clear Button', () => {
it('shows global Clear button when items are selected', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
cubes={[{ name: 'default.cube', display_name: 'Cube' }]}
/>,
);
const clearAllBtn = document.querySelector('.clear-all-btn');
expect(clearAllBtn).toBeInTheDocument();
expect(clearAllBtn.textContent).toBe('Clear');
});
it('calls onClearSelection when global Clear is clicked', () => {
const onClearSelection = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
cubes={[{ name: 'default.cube', display_name: 'Cube' }]}
onClearSelection={onClearSelection}
/>,
);
const clearAllBtn = document.querySelector('.clear-all-btn');
fireEvent.click(clearAllBtn);
expect(onClearSelection).toHaveBeenCalled();
});
it('clears metrics and dimensions if no onClearSelection provided', () => {
const onMetricsChange = vi.fn();
const onDimensionsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
selectedDimensions={['default.date_dim.dateint']}
onMetricsChange={onMetricsChange}
onDimensionsChange={onDimensionsChange}
cubes={[{ name: 'default.cube', display_name: 'Cube' }]}
/>,
);
const clearAllBtn = document.querySelector('.clear-all-btn');
fireEvent.click(clearAllBtn);
expect(onMetricsChange).toHaveBeenCalledWith([]);
expect(onDimensionsChange).toHaveBeenCalledWith([]);
});
});
describe('Dimension Path Display', () => {
it('shows dimension path when path has multiple segments', () => {
const dimensionsWithPath = [
{
name: 'default.date_dim.dateint',
type: 'timestamp',
path: ['default.date_dim.dateint'],
},
];
render(
<SelectionPanel
{...defaultProps}
dimensions={dimensionsWithPath}
selectedMetrics={['default.test']}
/>,
);
// Display name (last 2 segments) appears in the dimension item
expect(
screen.getAllByText('date_dim.dateint').length,
).toBeGreaterThanOrEqual(1);
});
});
describe('Namespace Sorting Logic', () => {
it('prioritizes namespaces that start with search term', () => {
const metricsWithNamespaces = [
'zebra.metric1',
'alpha.metric2',
'alpha_test.metric3',
'beta.metric4',
];
render(
<SelectionPanel {...defaultProps} metrics={metricsWithNamespaces} />,
);
const searchInput = screen.getByPlaceholderText('Search metrics...');
fireEvent.change(searchInput, { target: { value: 'alpha' } });
// Alpha namespace should be expanded first since it starts with 'alpha'
const namespaces = document.querySelectorAll('.namespace-header');
expect(namespaces.length).toBeGreaterThan(0);
});
it('sorts namespaces with more matching items higher', () => {
const metricsWithNamespaces = [
'default.test_metric1',
'default.test_metric2',
'default.test_metric3',
'other.test_metric4',
];
render(
<SelectionPanel {...defaultProps} metrics={metricsWithNamespaces} />,
);
const searchInput = screen.getByPlaceholderText('Search metrics...');
fireEvent.change(searchInput, { target: { value: 'test' } });
// Should show namespaces - default has more matching items
expect(screen.getByText('default')).toBeInTheDocument();
expect(screen.getByText('other')).toBeInTheDocument();
});
it('sorts namespaces alphabetically when other criteria are equal', () => {
const metricsWithNamespaces = [
'zebra.metric1',
'alpha.metric2',
'beta.metric3',
];
render(
<SelectionPanel {...defaultProps} metrics={metricsWithNamespaces} />,
);
// Namespaces should be available
expect(screen.getByText('alpha')).toBeInTheDocument();
expect(screen.getByText('beta')).toBeInTheDocument();
expect(screen.getByText('zebra')).toBeInTheDocument();
});
});
describe('Dimension Sorting Logic', () => {
it('prioritizes dimensions that start with search term', () => {
const sortableDimensions = [
{ name: 'default.zebra.column', path: [] },
{ name: 'default.alpha.column', path: [] },
{ name: 'default.date_dim.alpha_col', path: [] },
];
render(
<SelectionPanel
{...defaultProps}
dimensions={sortableDimensions}
selectedMetrics={['default.test']}
/>,
);
const searchInput = screen.getByPlaceholderText('Search dimensions...');
fireEvent.change(searchInput, { target: { value: 'alpha' } });
// Should show matching dimensions
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes.length).toBeGreaterThan(0);
});
it('sorts dimensions alphabetically by short name', () => {
const sortableDimensions = [
{ name: 'default.zebra.col', path: [] },
{ name: 'default.alpha.col', path: [] },
{ name: 'default.beta.col', path: [] },
];
render(
<SelectionPanel
{...defaultProps}
dimensions={sortableDimensions}
selectedMetrics={['default.test']}
/>,
);
const searchInput = screen.getByPlaceholderText('Search dimensions...');
fireEvent.change(searchInput, { target: { value: 'col' } });
// All three should be visible
expect(screen.getByText('alpha.col')).toBeInTheDocument();
expect(screen.getByText('beta.col')).toBeInTheDocument();
expect(screen.getByText('zebra.col')).toBeInTheDocument();
});
it('handles dimensions with prefix matches before contains matches', () => {
const sortableDimensions = [
{ name: 'default.country_code', path: [] },
{ name: 'default.customer.country', path: [] },
];
render(
<SelectionPanel
{...defaultProps}
dimensions={sortableDimensions}
selectedMetrics={['default.test']}
/>,
);
const searchInput = screen.getByPlaceholderText('Search dimensions...');
fireEvent.change(searchInput, { target: { value: 'country' } });
// Both should be visible
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes.length).toBe(2);
});
});
describe('Dimensions Chips Toggle', () => {
it('shows "Show all" button when many dimensions are selected', () => {
const manyDimensions = Array.from({ length: 15 }, (_, i) => ({
name: `default.dim_${i}`,
path: [],
}));
render(
<SelectionPanel
{...defaultProps}
dimensions={manyDimensions}
selectedMetrics={['default.test']}
selectedDimensions={manyDimensions.map(d => d.name)}
/>,
);
expect(screen.getByText('Show all')).toBeInTheDocument();
});
it('toggles dimension chips expansion', () => {
const manyDimensions = Array.from({ length: 15 }, (_, i) => ({
name: `default.dim_${i}`,
path: [],
}));
render(
<SelectionPanel
{...defaultProps}
dimensions={manyDimensions}
selectedMetrics={['default.test']}
selectedDimensions={manyDimensions.map(d => d.name)}
/>,
);
// Click to expand
const expandBtn = screen.getByText('Show all');
fireEvent.click(expandBtn);
// Should show "Show less"
expect(screen.getByText('Show less')).toBeInTheDocument();
// Click to collapse
fireEvent.click(screen.getByText('Show less'));
// Should show "Show all" again
expect(screen.getByText('Show all')).toBeInTheDocument();
});
});
describe('Toggle Namespace', () => {
it('toggles namespace expansion state', () => {
render(<SelectionPanel {...defaultProps} />);
// Click to expand 'default'
fireEvent.click(screen.getByText('default'));
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
// Click again to collapse
fireEvent.click(screen.getByText('default'));
expect(screen.queryByText('num_repair_orders')).not.toBeInTheDocument();
// Click again to expand
fireEvent.click(screen.getByText('default'));
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
});
it('allows multiple namespaces to be expanded', () => {
render(<SelectionPanel {...defaultProps} />);
// Expand both default and sales
fireEvent.click(screen.getByText('default'));
fireEvent.click(screen.getByText('sales'));
// Both should show their metrics
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
expect(screen.getByText('revenue')).toBeInTheDocument();
});
});
describe('Remove Dimension from Selected', () => {
it('removes dimension when clicking X on dimension chip', () => {
const onDimensionsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={[
'default.date_dim.dateint',
'default.date_dim.month',
'default.date_dim.year',
]}
onDimensionsChange={onDimensionsChange}
/>,
);
// Find and click remove button for dateint
const removeBtn = screen.getByTitle('Remove date_dim.dateint');
fireEvent.click(removeBtn);
expect(onDimensionsChange).toHaveBeenCalledWith([
'default.date_dim.month',
'default.date_dim.year',
]);
});
});
describe('Toggle Dimension Selection', () => {
it('removes dimension when unchecking already selected dimension', () => {
const onDimensionsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={['default.date_dim.dateint']}
onDimensionsChange={onDimensionsChange}
/>,
);
const checkbox = screen.getByRole('checkbox', { name: /dateint/i });
fireEvent.click(checkbox);
expect(onDimensionsChange).toHaveBeenCalledWith([]);
});
});
describe('Filters Section', () => {
it('renders filter input with placeholder', () => {
render(<SelectionPanel {...defaultProps} onFiltersChange={vi.fn()} />);
expect(
screen.getByPlaceholderText("e.g. v3.date.date_id >= '2024-01-01'"),
).toBeInTheDocument();
});
it('renders "Add" button for filters', () => {
render(<SelectionPanel {...defaultProps} onFiltersChange={vi.fn()} />);
expect(screen.getByText('Add')).toBeInTheDocument();
});
it('Add button is disabled when filter input is empty', () => {
render(<SelectionPanel {...defaultProps} onFiltersChange={vi.fn()} />);
expect(screen.getByText('Add')).toBeDisabled();
});
it('Add button is enabled when filter input has text', () => {
render(<SelectionPanel {...defaultProps} onFiltersChange={vi.fn()} />);
const filterInput = screen.getByPlaceholderText(
"e.g. v3.date.date_id >= '2024-01-01'",
);
fireEvent.change(filterInput, {
target: { value: "date >= '2024-01-01'" },
});
expect(screen.getByText('Add')).not.toBeDisabled();
});
it('calls onFiltersChange when Add button is clicked with non-empty input (lines 281-284)', () => {
const onFiltersChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
filters={[]}
onFiltersChange={onFiltersChange}
/>,
);
const filterInput = screen.getByPlaceholderText(
"e.g. v3.date.date_id >= '2024-01-01'",
);
fireEvent.change(filterInput, {
target: { value: "date >= '2024-01-01'" },
});
fireEvent.click(screen.getByText('Add'));
expect(onFiltersChange).toHaveBeenCalledWith(["date >= '2024-01-01'"]);
});
it('clears filter input after adding a filter', () => {
const onFiltersChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
filters={[]}
onFiltersChange={onFiltersChange}
/>,
);
const filterInput = screen.getByPlaceholderText(
"e.g. v3.date.date_id >= '2024-01-01'",
);
fireEvent.change(filterInput, { target: { value: 'status = active' } });
fireEvent.click(screen.getByText('Add'));
expect(filterInput.value).toBe('');
});
it('does not add duplicate filters', () => {
const onFiltersChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
filters={["date >= '2024-01-01'"]}
onFiltersChange={onFiltersChange}
/>,
);
const filterInput = screen.getByPlaceholderText(
"e.g. v3.date.date_id >= '2024-01-01'",
);
fireEvent.change(filterInput, {
target: { value: "date >= '2024-01-01'" },
});
fireEvent.click(screen.getByText('Add'));
expect(onFiltersChange).not.toHaveBeenCalled();
});
it('adds filter on Enter key press (lines 289-291)', () => {
const onFiltersChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
filters={[]}
onFiltersChange={onFiltersChange}
/>,
);
const filterInput = screen.getByPlaceholderText(
"e.g. v3.date.date_id >= '2024-01-01'",
);
fireEvent.change(filterInput, { target: { value: 'status = active' } });
fireEvent.keyDown(filterInput, { key: 'Enter' });
expect(onFiltersChange).toHaveBeenCalledWith(['status = active']);
});
it('does not add filter on non-Enter key press', () => {
const onFiltersChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
filters={[]}
onFiltersChange={onFiltersChange}
/>,
);
const filterInput = screen.getByPlaceholderText(
"e.g. v3.date.date_id >= '2024-01-01'",
);
fireEvent.change(filterInput, { target: { value: 'status = active' } });
fireEvent.keyDown(filterInput, { key: 'Tab' });
expect(onFiltersChange).not.toHaveBeenCalled();
});
it('renders existing filter chips (lines 665-686)', () => {
render(
<SelectionPanel
{...defaultProps}
filters={["date >= '2024-01-01'", 'status = active']}
onFiltersChange={vi.fn()}
/>,
);
expect(screen.getByText("date >= '2024-01-01'")).toBeInTheDocument();
expect(screen.getByText('status = active')).toBeInTheDocument();
});
it('calls onFiltersChange when filter chip remove button is clicked (lines 296-297)', () => {
const onFiltersChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
filters={["date >= '2024-01-01'", 'status = active']}
onFiltersChange={onFiltersChange}
/>,
);
// Get all "Remove filter" buttons and click the first one
const removeBtns = screen.getAllByTitle('Remove filter');
fireEvent.click(removeBtns[0]);
expect(onFiltersChange).toHaveBeenCalledWith(['status = active']);
});
it('shows 0 applied count when no filters', () => {
render(<SelectionPanel {...defaultProps} />);
expect(screen.getByText('0 applied')).toBeInTheDocument();
});
it('shows correct applied count when filters present', () => {
render(
<SelectionPanel
{...defaultProps}
filters={['filter1', 'filter2']}
onFiltersChange={vi.fn()}
/>,
);
expect(screen.getByText('2 applied')).toBeInTheDocument();
});
});
describe('Engine Selection (line 709)', () => {
it('renders engine pills', () => {
render(<SelectionPanel {...defaultProps} />);
expect(screen.getByText('Auto')).toBeInTheDocument();
expect(screen.getByText('Druid')).toBeInTheDocument();
expect(screen.getByText('Trino')).toBeInTheDocument();
});
it('highlights the active engine pill', () => {
render(<SelectionPanel {...defaultProps} selectedEngine="druid" />);
const druidPill = screen.getByText('Druid').closest('button');
expect(druidPill).toHaveClass('active');
});
it('auto engine pill is active when selectedEngine is null', () => {
render(<SelectionPanel {...defaultProps} selectedEngine={null} />);
const autoPill = screen.getByText('Auto').closest('button');
expect(autoPill).toHaveClass('active');
});
it('calls onEngineChange when engine pill is clicked', () => {
const onEngineChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedEngine={null}
onEngineChange={onEngineChange}
/>,
);
fireEvent.click(screen.getByText('Trino'));
expect(onEngineChange).toHaveBeenCalledWith('trino');
});
it('does not throw when onEngineChange is not provided', () => {
render(
<SelectionPanel
{...defaultProps}
selectedEngine={null}
onEngineChange={undefined}
/>,
);
expect(() => {
fireEvent.click(screen.getByText('Druid'));
}).not.toThrow();
});
});
describe('Metrics combobox-input click handler (line 386)', () => {
it('clicking combobox-input container focuses the metrics search input', () => {
render(<SelectionPanel {...defaultProps} />);
const comboboxInputs = document.querySelectorAll('.combobox-input');
// The first combobox-input is for metrics
expect(comboboxInputs[0]).toBeInTheDocument();
// Click the container — should not throw
expect(() => fireEvent.click(comboboxInputs[0])).not.toThrow();
});
});
describe('Metrics search input stopPropagation (line 423)', () => {
it('clicking metrics search input does not bubble to combobox container', () => {
render(<SelectionPanel {...defaultProps} />);
const searchInput = screen.getByPlaceholderText('Search metrics...');
// Clicking the input itself should not throw
expect(() => fireEvent.click(searchInput)).not.toThrow();
});
});
describe('Clear metrics button (lines 440-441)', () => {
it('shows inline Clear button when metrics are selected', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
/>,
);
// The combobox-action Clear button inside metrics combobox
const clearBtns = document.querySelectorAll('.combobox-action');
const clearMetricsBtns = Array.from(clearBtns).filter(
btn => btn.textContent === 'Clear',
);
expect(clearMetricsBtns.length).toBeGreaterThanOrEqual(1);
});
it('calls onMetricsChange([]) when inline Clear is clicked', () => {
const onMetricsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={[
'default.num_repair_orders',
'default.avg_repair_price',
]}
onMetricsChange={onMetricsChange}
/>,
);
// Find the Clear button inside metrics combobox (not the namespace-level Clear)
const metricsSection = document.querySelector('.combobox-input');
const clearBtn = metricsSection.querySelector('.combobox-action');
fireEvent.click(clearBtn);
expect(onMetricsChange).toHaveBeenCalledWith([]);
});
});
describe('Dimensions combobox-input click handler (line 546)', () => {
it('clicking dimensions combobox-input container does not throw', () => {
render(
<SelectionPanel {...defaultProps} selectedMetrics={['default.test']} />,
);
const comboboxInputs = document.querySelectorAll('.combobox-input');
// The second combobox-input is for dimensions
if (comboboxInputs.length >= 2) {
expect(() => fireEvent.click(comboboxInputs[1])).not.toThrow();
}
});
});
describe('Dimensions search input stopPropagation (line 586)', () => {
it('clicking dimensions search input does not throw', () => {
render(
<SelectionPanel {...defaultProps} selectedMetrics={['default.test']} />,
);
const searchInput = screen.getByPlaceholderText('Search dimensions...');
expect(() => fireEvent.click(searchInput)).not.toThrow();
});
});
describe('Clear dimensions button (lines 603-604)', () => {
it('shows inline Clear button for dimensions when dimensions are selected', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={['default.date_dim.dateint']}
/>,
);
const clearBtns = document.querySelectorAll('.combobox-action');
const clearDimBtns = Array.from(clearBtns).filter(
btn => btn.textContent === 'Clear',
);
expect(clearDimBtns.length).toBeGreaterThanOrEqual(1);
});
it('calls onDimensionsChange([]) when dimensions inline Clear is clicked', () => {
const onDimensionsChange = vi.fn();
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.test']}
selectedDimensions={[
'default.date_dim.dateint',
'default.date_dim.month',
]}
onDimensionsChange={onDimensionsChange}
/>,
);
// The combobox-action buttons — the last set belongs to dimensions
const comboboxActions = document.querySelectorAll('.combobox-action');
// Last Clear button is for dimensions (metrics Clear is first if only one metric selected
// but here no many-metrics Show all button, so first Clear = metrics, second = dims)
const clearBtns = Array.from(comboboxActions).filter(
btn => btn.textContent === 'Clear',
);
// Click the last Clear button (dimensions)
fireEvent.click(clearBtns[clearBtns.length - 1]);
expect(onDimensionsChange).toHaveBeenCalledWith([]);
});
});
describe('Run Query Section', () => {
it('renders Run Query button', () => {
render(<SelectionPanel {...defaultProps} />);
expect(screen.getByText('Run Query')).toBeInTheDocument();
});
it('Run Query button is disabled when canRunQuery is false', () => {
render(<SelectionPanel {...defaultProps} canRunQuery={false} />);
expect(screen.getByText('Run Query').closest('button')).toBeDisabled();
});
it('Run Query button is enabled when canRunQuery is true', () => {
render(
<SelectionPanel
{...defaultProps}
canRunQuery={true}
onRunQuery={vi.fn()}
/>,
);
expect(
screen.getByText('Run Query').closest('button'),
).not.toBeDisabled();
});
it('shows running state when queryLoading is true', () => {
render(
<SelectionPanel
{...defaultProps}
canRunQuery={true}
queryLoading={true}
onRunQuery={vi.fn()}
/>,
);
expect(screen.getByText('Running...')).toBeInTheDocument();
});
it('calls onRunQuery when Run Query is clicked', () => {
const onRunQuery = vi.fn();
render(
<SelectionPanel
{...defaultProps}
canRunQuery={true}
onRunQuery={onRunQuery}
/>,
);
fireEvent.click(screen.getByText('Run Query').closest('button'));
expect(onRunQuery).toHaveBeenCalled();
});
it('shows metric hint when query cannot run (no metrics selected)', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={[]}
canRunQuery={false}
/>,
);
expect(
screen.getByText('Select at least one metric to run a query'),
).toBeInTheDocument();
});
it('shows no run hint once a metric is selected (dimensions optional)', () => {
render(
<SelectionPanel
{...defaultProps}
selectedMetrics={['default.num_repair_orders']}
selectedDimensions={[]}
canRunQuery={true}
onRunQuery={vi.fn()}
/>,
);
expect(
screen.queryByText('Select at least one metric to run a query'),
).not.toBeInTheDocument();
expect(
screen.getByText('Run Query').closest('button'),
).not.toBeDisabled();
});
});
});