import React from 'react'; import { describe, expect, it } from 'vitest'; import { renderToStaticMarkup } from 'react-dom/server'; import { RequestEventsDetailsCard } from './RequestEventsDetailsCard'; import type { UsageEvent } from '@/lib/types'; const events: UsageEvent[] = [ { id: 101, timestamp: '2026-04-23T02:00:00.000Z', model: 'claude-sonnet', source: 'Provider A', source_raw: 'source-a', source_type: 'openai', auth_index: '1', failed: false, latency_ms: 120, tokens: { input_tokens: 100, output_tokens: 60, reasoning_tokens: 20, cached_tokens: 20, total_tokens: 200, }, }, ]; const renderCard = (props: Partial> = {}) => renderToStaticMarkup( undefined} onPageSizeChange={() => undefined} onModelFilterChange={() => undefined} onSourceFilterChange={() => undefined} onResultFilterChange={() => undefined} {...props} />, ); const countOccurrences = (text: string, value: string) => text.split(value).length - 1; describe('RequestEventsDetailsCard pagination', () => { it('renders total events, current page, page size options, and disabled page buttons', () => { const html = renderCard(); expect(html).toContain('120 total events'); expect(html).toContain('Page (1/6)'); expect(html).toContain('20'); expect(html).toContain('50'); expect(html).toContain('100'); expect(html).toContain('500'); expect(html).toContain('1000'); expect(html).toContain('Previous'); expect(html).toContain('Next'); expect(html).toContain('disabled'); }); it('uses backend source values while showing resolved source labels', () => { const html = renderCard({ sourceFilter: 'source-a' }); expect(countOccurrences(html, 'Provider A')).toBeGreaterThanOrEqual(2); expect(html).toContain('aria-label="Source">Provider A'); }); it('uses backend model and source options instead of current page grouping', () => { const html = renderCard({ modelFilter: 'claude-opus', sourceFilter: 'source-b' }); expect(html).toContain('aria-label="Model">claude-opus'); expect(html).toContain('aria-label="Source">Provider B'); }); it('renders a Result filter and no Credential filter control', () => { const html = renderCard({ resultFilter: 'failed' }); expect(html).toContain('aria-label="Result"'); expect(html).toContain('Failure'); expect(html).not.toContain('aria-label="Credential"'); }); it('keeps selected filters visible when backend options do not include them', () => { const html = renderCard({ modelFilter: 'claude-haiku', sourceFilter: 'source-c', }); expect(html).toContain('claude-haiku'); expect(html).toContain('source-c'); }); it('falls back to a computed page count when metadata is not populated', () => { const html = renderCard({ totalPages: 0, totalCount: 120, pageSize: 20 }); expect(html).toContain('Page (1/6)'); }); it('groups filters and pager controls in a compact toolbar', () => { const html = renderCard(); expect(html).toContain('_requestEventsFiltersGroup_'); expect(html).toContain('_requestEventsPaginationControls_'); expect(html).toContain('_requestEventsPaginationItem_'); expect(html).toContain('_requestEventsPagerButton_'); expect(html).toContain('_requestEventsPageSizeSelectCompact_'); expect(html).toContain('_requestEventsActions_'); expect(html).toContain('_requestEventsTableMeta_'); }); it('hides export buttons while keeping clear filters available', () => { const html = renderCard({ modelFilter: 'claude-sonnet' }); expect(html).toContain('Clear Filters'); expect(html).not.toContain('Export CSV'); expect(html).not.toContain('Export JSON'); }); });