Public source validation / validate (push) Failing after 3m8s
82 lines
3.6 KiB
TypeScript
82 lines
3.6 KiB
TypeScript
import { render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import { MetricWidget, RankedListWidget, StatusGridWidget, metricStatus, type MetricWidgetProps } from '../../src/MetricWidgets';
|
|
|
|
const now = Date.parse('2026-08-10T04:00:00Z');
|
|
const freshPoint = { timestamp: now, value: 24, freshness: 'fresh' as const, labels: { host: 'tower' } };
|
|
|
|
function props(overrides: Partial<MetricWidgetProps> = {}): MetricWidgetProps {
|
|
return {
|
|
kind: 'stat',
|
|
series: [{ key: 'tower', points: [freshPoint] }],
|
|
freshness: 'fresh',
|
|
expectedStepSeconds: 15,
|
|
availability: 'success',
|
|
metricName: 'host.cpu.utilization',
|
|
visualization: { unit: 'percent', decimals: 0 },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('metricStatus ADR-0008 invariant', () => {
|
|
it('marks only fresh, available data as ready', () => {
|
|
expect(metricStatus(props())).toMatchObject({ tone: 'ready' });
|
|
});
|
|
|
|
it.each([
|
|
props({ availability: 'idle' }),
|
|
props({ availability: 'loading' }),
|
|
props({ availability: 'connecting' }),
|
|
props({ availability: 'error' }),
|
|
props({ series: [] }),
|
|
props({ freshness: 'stale' }),
|
|
props({ freshness: 'unavailable' }),
|
|
props({ series: [{ key: 'tower', points: [{ ...freshPoint, freshness: 'delayed' }] }] }),
|
|
])('never presents missing, stale or unavailable telemetry as ready', (value) => {
|
|
expect(metricStatus(value).tone).toBe('unknown');
|
|
});
|
|
|
|
it('renders an explicit unavailable notice without a numeric value', () => {
|
|
render(<MetricWidget {...props({ freshness: 'unavailable', series: [], availability: 'error', error: 'Bron tijdelijk onbereikbaar.' })} />);
|
|
expect(screen.getByText('Bron tijdelijk onbereikbaar.')).toBeVisible();
|
|
expect(screen.getByText(/De waarde wordt niet als gezond geïnterpreteerd/)).toBeVisible();
|
|
expect(screen.queryByText('24 %')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('interactive widgets', () => {
|
|
it('presents metric series labels as human-readable values without raw JSON', () => {
|
|
const rawKey = '{"interface":"eth0","host":"tower"}';
|
|
render(<MetricWidget {...props({
|
|
kind: 'timeseries',
|
|
series: [{ key: rawKey, points: [
|
|
{ ...freshPoint, timestamp: now - 15_000, labels: { __name__: 'host.network.receive', host: 'tower', interface: 'eth0' } },
|
|
{ ...freshPoint, labels: { __name__: 'host.network.receive', host: 'tower', interface: 'eth0' } },
|
|
] }],
|
|
metricName: 'host.network.receive',
|
|
})} />);
|
|
|
|
expect(screen.getByRole('list', { name: 'Legenda' })).toHaveTextContent('tower · eth0');
|
|
expect(screen.getByRole('img', { name: /Tijdreeks voor Goedgekeurde meting/ })).toBeVisible();
|
|
expect(document.querySelector('.metric-chart-line')).toHaveAttribute('d', 'M 28.000 192.000 L 628.000 192.000');
|
|
expect(document.body).not.toHaveTextContent(rawKey);
|
|
});
|
|
|
|
it('supports keyboard activation of ranked rows', async () => {
|
|
const onSelect = vi.fn();
|
|
render(<RankedListWidget items={[{ id: 'disk-1', label: 'Disk 1', value: '78%' }]} onSelect={onSelect} />);
|
|
const row = screen.getByRole('button', { name: /Disk 1/ });
|
|
row.focus();
|
|
await userEvent.keyboard('{Enter}');
|
|
expect(onSelect).toHaveBeenCalledWith('disk-1');
|
|
});
|
|
|
|
it('localizes status codes in status-grid fallbacks', () => {
|
|
render(<StatusGridWidget items={[{ id: 'pool-1', label: 'Cachepool', status: 'degraded' }]} onSelect={vi.fn()} />);
|
|
expect(screen.getByText('Verstoord')).toBeVisible();
|
|
expect(screen.queryByText('degraded')).not.toBeInTheDocument();
|
|
});
|
|
});
|