mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 08:15:17 +00:00
145 lines
4.5 KiB
TypeScript
145 lines
4.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { SessionStatsCard } from '@/components/analytics/session-stats-card';
|
|
import type { PaginatedSessions, Session } from '@/hooks/use-usage';
|
|
import { AllProviders } from '@tests/setup/test-utils';
|
|
|
|
vi.mock('date-fns', async () => {
|
|
const actual = await vi.importActual('date-fns');
|
|
return {
|
|
...actual,
|
|
formatDistanceToNow: vi.fn(() => '27 minutes ago'),
|
|
};
|
|
});
|
|
|
|
function buildSession(overrides: Partial<Session> = {}): Session {
|
|
return {
|
|
sessionId: 'session-1',
|
|
projectPath: '/Users/kaitran/CloudPersonal/worktrees/ccs-cli/feature-branch',
|
|
inputTokens: 1_500,
|
|
outputTokens: 2_500,
|
|
cost: 0.08,
|
|
lastActivity: '2026-04-26T14:00:00.000Z',
|
|
modelsUsed: ['claude-sonnet-4'],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildPaginatedSessions(overrides: Partial<PaginatedSessions> = {}): PaginatedSessions {
|
|
return {
|
|
sessions: [buildSession()],
|
|
total: 1,
|
|
limit: 50,
|
|
offset: 0,
|
|
hasMore: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('SessionStatsCard', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renders a loading skeleton', () => {
|
|
const { container } = render(<SessionStatsCard data={undefined} isLoading />, {
|
|
wrapper: AllProviders,
|
|
});
|
|
|
|
expect(container.querySelector('[data-slot="skeleton"]')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the empty state with the live paginated contract', () => {
|
|
render(<SessionStatsCard data={buildPaginatedSessions({ sessions: [], total: 0 })} />, {
|
|
wrapper: AllProviders,
|
|
});
|
|
|
|
expect(screen.getByText('Session Stats')).toBeInTheDocument();
|
|
expect(screen.getByText('No session data available')).toBeInTheDocument();
|
|
});
|
|
|
|
it('keeps subset session metrics explicitly sample-scoped when pagination truncates the result set', () => {
|
|
const data = buildPaginatedSessions({
|
|
sessions: [
|
|
buildSession({ sessionId: 'session-1', cost: 0.08 }),
|
|
buildSession({
|
|
sessionId: 'session-2',
|
|
projectPath: '/Users/kaitran/projects/platform/worktrees/kai-fix',
|
|
inputTokens: 2_000,
|
|
outputTokens: 3_000,
|
|
cost: 0.12,
|
|
target: 'codex',
|
|
}),
|
|
buildSession({
|
|
sessionId: 'session-3',
|
|
projectPath: '/Users/kaitran/projects/share-pi',
|
|
inputTokens: 1_000,
|
|
outputTokens: 2_000,
|
|
cost: 0.05,
|
|
}),
|
|
],
|
|
total: 9,
|
|
hasMore: true,
|
|
});
|
|
|
|
render(<SessionStatsCard data={data} />, { wrapper: AllProviders });
|
|
|
|
expect(screen.getByText('Sampled Sessions')).toBeInTheDocument();
|
|
expect(screen.getByText('3')).toBeInTheDocument();
|
|
expect(screen.getByText('9 total')).toBeInTheDocument();
|
|
expect(screen.getByText('Recent Avg Cost')).toBeInTheDocument();
|
|
expect(screen.getAllByText('$0.08').length).toBeGreaterThan(0);
|
|
expect(screen.getByText('codex')).toBeInTheDocument();
|
|
});
|
|
|
|
it('keeps the overall average label when the full result set is loaded', () => {
|
|
const data = buildPaginatedSessions({
|
|
sessions: [
|
|
buildSession({ sessionId: 'session-1', cost: 0.04 }),
|
|
buildSession({ sessionId: 'session-2', cost: 0.06 }),
|
|
],
|
|
total: 2,
|
|
hasMore: false,
|
|
});
|
|
|
|
render(<SessionStatsCard data={data} />, { wrapper: AllProviders });
|
|
|
|
expect(screen.getByText('Avg Cost/Session')).toBeInTheDocument();
|
|
expect(screen.queryByText('Recent Avg Cost')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('formats project names from worktree paths without regressing the live fixture shape', () => {
|
|
const data = buildPaginatedSessions({
|
|
sessions: [
|
|
buildSession({
|
|
projectPath:
|
|
'/Users/kaitran/Developer/ExaDev/Clients/Architect/repositories/architect/worktrees/2026-01-08',
|
|
}),
|
|
],
|
|
});
|
|
|
|
render(<SessionStatsCard data={data} />, { wrapper: AllProviders });
|
|
|
|
expect(
|
|
screen.getByTitle(
|
|
'/Users/kaitran/Developer/ExaDev/Clients/Architect/repositories/architect/worktrees/2026-01-08'
|
|
)
|
|
).toHaveTextContent('2026-01-08');
|
|
});
|
|
|
|
it('formats visible token counts from input plus output only', () => {
|
|
const data = buildPaginatedSessions({
|
|
sessions: [
|
|
buildSession({
|
|
inputTokens: 1_500_000,
|
|
outputTokens: 500_000,
|
|
}),
|
|
],
|
|
});
|
|
|
|
render(<SessionStatsCard data={data} />, { wrapper: AllProviders });
|
|
|
|
expect(screen.getByText('2.0M toks')).toBeInTheDocument();
|
|
});
|
|
});
|