mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-03 21:35:13 +00:00
Some checks are pending
CI / semgrep (push) Waiting to run
Part 1 (src): expose two add-only, optional aggregations on the menubar payload's `current`, computed on the unscoped all-provider path from the surviving-session parse (carried history cannot contribute, as expected). - current.pullRequests: aggregateByPr rows (top 20 by cost) plus the multi-link-safe distinctCost/distinctSessions. Rows are by-reference, so they are never summed. - current.byBranch: new aggregateByBranch, per-branch spend (top 15 by cost) that carries each session's last-seen git branch forward across its turns. The cache stores a turn's branch only when it changes, so the parser now resolves the branch at reconstruction (before the date slice) and records SessionSummary.everHadBranch from the full transcript. That lets the report keep a branch-bearing session's pre-branch spend in an explicit null row even when the range clipped the anchor turn, while a provider that never captures a branch still contributes nothing. Part 2 (app): new "Pull requests" sidebar entry between Sessions and Spend (shortcuts reflow to number 8 for Plans). It renders the pullRequests rows as a refined table with the PR label linking out via openExternal, cost, sessions, calls and an active-date span, plus a footnote stating the distinct total and the by-reference attribution. A quiet explanatory line shows when no PR links exist, never a fake table. Tests: aggregateByBranch unit tests (carry-forward, null bucket, clipped anchor, by-reference session counts) and the Pull requests component tests (table, external link, footnote, both empty states). Nav-reflow assertions in Sidebar and App tests updated. Co-authored-by: reviewer <review@local>
38 lines
1.9 KiB
TypeScript
38 lines
1.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
|
|
import { Sidebar } from './Sidebar'
|
|
|
|
describe('Sidebar', () => {
|
|
it('renders all nine nav items in the desktop order', () => {
|
|
render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
const labels = screen.getAllByRole('button').map(item => item.textContent?.replace(/⌘[\d,]/, ''))
|
|
expect(labels).toEqual(['Overview', 'Sessions', 'Pull requests', 'Spend', 'Optimize', 'Models', 'Compare', 'Plans', 'Settings'])
|
|
expect(screen.getByRole('button', { name: /Sessions.*⌘2/ })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: /Pull requests.*⌘3/ })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: /Compare.*⌘7/ })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: /Plans.*⌘8/ })).toBeInTheDocument()
|
|
})
|
|
|
|
it('calls onNavigate with the section id when a nav item is clicked', () => {
|
|
const onNavigate = vi.fn()
|
|
render(<Sidebar active="overview" onNavigate={onNavigate} />)
|
|
fireEvent.click(screen.getByRole('button', { name: /Spend/ }))
|
|
expect(onNavigate).toHaveBeenCalledWith('spend')
|
|
})
|
|
|
|
it('marks the active item with the "on" class', () => {
|
|
render(<Sidebar active="models" onNavigate={() => {}} />)
|
|
expect(screen.getByRole('button', { name: /Models/ })).toHaveClass('on')
|
|
expect(screen.getByRole('button', { name: /Overview/ })).not.toHaveClass('on')
|
|
})
|
|
|
|
it('renders the brand flame mark, static under the closed motion gate', () => {
|
|
const { container } = render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
const flame = container.querySelector('.app .flamemark')
|
|
expect(flame?.tagName.toLowerCase()).toBe('img')
|
|
// motionEnabled() is off under vitest, so the idle flicker never attaches.
|
|
expect(container.querySelector('.fm-flicker')).toBeNull()
|
|
})
|
|
})
|