mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-21 05:23:19 +00:00
The sidebar's bottom corner held About and a row of five brand glyphs. The glyphs were the least useful thing on that screen: they are visited once, and About already listed every one of them under Links. So they are only there now, and the corner carries the two things a Windows user does want at hand: whether CodeBurn sits in the notification area, and whether the Capacity Dock rail is on the screen edge. Both are on by default and neither renders at all until the main process says this build has a tray app staged, which is why there is no placeholder row and no greyed-out switch: on macOS, on Linux, and in a dev build with nothing staged, the corner is exactly what it always was. The switch is the shared one from Settings and onboarding, at 30x17 rather than 38x22 because a 186px sidebar has no room for the full size beside a label, and keyed off the theme tokens rather than the white overlays the bare shape uses on the dark onboarding sheet.
156 lines
7 KiB
TypeScript
156 lines
7 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest'
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
|
|
import { Sidebar } from './Sidebar'
|
|
|
|
// The preload bridge is read once at module load, so the switches need it mocked rather than
|
|
// assigned onto `window` after the fact.
|
|
const bridge = vi.hoisted(() => ({
|
|
companionStatus: vi.fn(),
|
|
setMenuBarEnabled: vi.fn(),
|
|
setSidebarEnabled: vi.fn(),
|
|
openExternal: vi.fn(),
|
|
}))
|
|
vi.mock('../lib/ipc', () => ({ codeburn: bridge, normalizeCliError: (err: unknown) => err }))
|
|
|
|
function setPlatform(platform: string): void {
|
|
;(window as unknown as { codeburn?: { platform?: string } }).codeburn = { platform }
|
|
}
|
|
|
|
describe('Sidebar', () => {
|
|
beforeEach(() => {
|
|
bridge.companionStatus.mockResolvedValue({ supported: false, menuBar: false, sidebar: false, store: false })
|
|
})
|
|
|
|
afterEach(() => {
|
|
delete (window as unknown as { codeburn?: { platform?: string } }).codeburn
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it.each([
|
|
['darwin', '⌘'],
|
|
['win32', 'Ctrl+'],
|
|
] as const)('renders all nav items in the desktop order with %s keycaps', (platform, mod) => {
|
|
setPlatform(platform)
|
|
render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
const labels = screen.getAllByRole('button').map(item => item.textContent?.replace(/(⌘|Ctrl\+)[\d,.]/, ''))
|
|
expect(labels).toEqual(['Overview', 'Sessions', 'Pull requests', 'Spend', 'Optimize', 'Models', 'Compare', 'Plans', 'Settings', 'Plugins'])
|
|
expect(screen.getByRole('button', { name: new RegExp(`Sessions.*${esc(mod)}2`) })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: new RegExp(`Pull requests.*${esc(mod)}3`) })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: new RegExp(`Compare.*${esc(mod)}7`) })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: new RegExp(`Plans.*${esc(mod)}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()
|
|
})
|
|
|
|
it('keeps About in the corner and the social glyphs out of it', async () => {
|
|
const { container } = render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
expect(screen.getByRole('link', { name: 'About' })).toBeInTheDocument()
|
|
expect(container.querySelector('.foot .social')).toBeNull()
|
|
// They moved into the About modal, which lists them under Links.
|
|
fireEvent.click(screen.getByRole('link', { name: 'About' }))
|
|
expect(await screen.findByRole('link', { name: /GitHub/ })).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /LinkedIn/ })).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Sidebar companion switches', () => {
|
|
const SUPPORTED = { supported: true, menuBar: true, sidebar: true, store: false }
|
|
|
|
afterEach(() => { vi.clearAllMocks() })
|
|
|
|
async function renderSwitches(status = SUPPORTED) {
|
|
bridge.companionStatus.mockResolvedValue(status)
|
|
render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
return screen.findByRole('switch', { name: 'Menu bar' })
|
|
}
|
|
|
|
it('shows nothing where the main process reports no bundled tray app', async () => {
|
|
bridge.companionStatus.mockResolvedValue({ supported: false, menuBar: false, sidebar: false, store: false })
|
|
render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
|
|
await waitFor(() => expect(bridge.companionStatus).toHaveBeenCalled())
|
|
expect(screen.queryByRole('switch')).toBeNull()
|
|
})
|
|
|
|
it('survives a preload that has never heard of them', async () => {
|
|
bridge.companionStatus.mockRejectedValue(new Error('no such channel'))
|
|
render(<Sidebar active="overview" onNavigate={() => {}} />)
|
|
|
|
await waitFor(() => expect(bridge.companionStatus).toHaveBeenCalled())
|
|
expect(screen.queryByRole('switch')).toBeNull()
|
|
})
|
|
|
|
it('renders both switches on, in the sidebar corner', async () => {
|
|
const menuBar = await renderSwitches()
|
|
|
|
expect(menuBar).toHaveAttribute('aria-checked', 'true')
|
|
expect(screen.getByRole('switch', { name: 'Sidebar' })).toHaveAttribute('aria-checked', 'true')
|
|
})
|
|
|
|
it('turning Menu bar off sends false and renders the status that came back', async () => {
|
|
const menuBar = await renderSwitches()
|
|
bridge.setMenuBarEnabled.mockResolvedValue({ ...SUPPORTED, menuBar: false })
|
|
|
|
fireEvent.click(menuBar)
|
|
|
|
expect(bridge.setMenuBarEnabled).toHaveBeenCalledWith(false)
|
|
await waitFor(() => expect(menuBar).toHaveAttribute('aria-checked', 'false'))
|
|
expect(screen.getByRole('switch', { name: 'Sidebar' })).toHaveAttribute('aria-checked', 'true')
|
|
})
|
|
|
|
it('turning Sidebar off leaves Menu bar alone', async () => {
|
|
await renderSwitches()
|
|
bridge.setSidebarEnabled.mockResolvedValue({ ...SUPPORTED, sidebar: false })
|
|
|
|
fireEvent.click(screen.getByRole('switch', { name: 'Sidebar' }))
|
|
|
|
expect(bridge.setSidebarEnabled).toHaveBeenCalledWith(false)
|
|
await waitFor(() => expect(screen.getByRole('switch', { name: 'Sidebar' })).toHaveAttribute('aria-checked', 'false'))
|
|
expect(screen.getByRole('switch', { name: 'Menu bar' })).toHaveAttribute('aria-checked', 'true')
|
|
})
|
|
|
|
/// An install the person cancelled at the UAC prompt comes back unchanged, and the switch
|
|
/// has to show that rather than the state it optimistically painted.
|
|
it('stays where it was when the main process reports no change', async () => {
|
|
const menuBar = await renderSwitches({ ...SUPPORTED, menuBar: false })
|
|
bridge.setMenuBarEnabled.mockResolvedValue({ ...SUPPORTED, menuBar: false })
|
|
|
|
fireEvent.click(menuBar)
|
|
|
|
await waitFor(() => expect(bridge.setMenuBarEnabled).toHaveBeenCalledWith(true))
|
|
expect(menuBar).toHaveAttribute('aria-checked', 'false')
|
|
})
|
|
|
|
it('refuses a second click while one is still in flight', async () => {
|
|
const menuBar = await renderSwitches()
|
|
bridge.setMenuBarEnabled.mockReturnValue(new Promise(() => {}))
|
|
|
|
fireEvent.click(menuBar)
|
|
fireEvent.click(screen.getByRole('switch', { name: 'Sidebar' }))
|
|
|
|
expect(bridge.setMenuBarEnabled).toHaveBeenCalledTimes(1)
|
|
expect(bridge.setSidebarEnabled).not.toHaveBeenCalled()
|
|
})
|
|
})
|