2026-02-27 14:41:23 +01:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
2026-04-14 11:55:48 +02:00
|
|
|
import { act, render, screen, fireEvent } from '@testing-library/react'
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
import { StatusBar } from './StatusBar'
|
|
|
|
|
import type { VaultOption } from './StatusBar'
|
2026-02-27 14:41:23 +01:00
|
|
|
vi.mock('../utils/url', async () => {
|
|
|
|
|
const actual = await vi.importActual('../utils/url')
|
|
|
|
|
return { ...actual, openExternalUrl: vi.fn().mockResolvedValue(undefined) }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const { openExternalUrl } = await import('../utils/url') as typeof import('../utils/url') & { openExternalUrl: ReturnType<typeof vi.fn> }
|
|
|
|
|
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
const vaults: VaultOption[] = [
|
|
|
|
|
{ label: 'Main Vault', path: '/Users/luca/Laputa' },
|
|
|
|
|
{ label: 'Work Vault', path: '/Users/luca/Work' },
|
|
|
|
|
]
|
|
|
|
|
|
2026-04-14 11:55:48 +02:00
|
|
|
const installedAiAgentsStatus = {
|
|
|
|
|
claude_code: { status: 'installed' as const, version: '1.0.20' },
|
|
|
|
|
codex: { status: 'installed' as const, version: '0.37.0' },
|
|
|
|
|
}
|
|
|
|
|
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
describe('StatusBar', () => {
|
2026-02-27 14:41:23 +01:00
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-16 00:01:26 +02:00
|
|
|
it('does not display the bottom-bar note count readout', () => {
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
render(<StatusBar noteCount={9200} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
2026-04-16 00:01:26 +02:00
|
|
|
expect(screen.queryByText('9,200 notes')).not.toBeInTheDocument()
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
})
|
|
|
|
|
|
2026-03-02 01:50:41 +01:00
|
|
|
it('displays build number when provided', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} buildNumber="b223" />)
|
|
|
|
|
expect(screen.getByText('b223')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('displays fallback build number when not provided', () => {
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
2026-03-02 01:50:41 +01:00
|
|
|
expect(screen.getByText('b?')).toBeInTheDocument()
|
2026-02-26 20:14:46 +01:00
|
|
|
})
|
|
|
|
|
|
2026-03-03 19:46:37 +01:00
|
|
|
it('calls onCheckForUpdates when clicking build number', () => {
|
|
|
|
|
const onCheckForUpdates = vi.fn()
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} buildNumber="b281" onCheckForUpdates={onCheckForUpdates} />)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-build-number'))
|
|
|
|
|
expect(onCheckForUpdates).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('build number has "Check for updates" title', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} buildNumber="b281" onCheckForUpdates={vi.fn()} />)
|
|
|
|
|
expect(screen.getByTitle('Check for updates')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-26 20:14:46 +01:00
|
|
|
it('does not display branch name', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.queryByText('main')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-10 12:45:37 +02:00
|
|
|
it('shows Feedback button when callback is provided', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenFeedback={vi.fn()} />)
|
|
|
|
|
expect(screen.getByTestId('status-feedback')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText('Feedback')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calls onOpenFeedback when Feedback is clicked', () => {
|
|
|
|
|
const onOpenFeedback = vi.fn()
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenFeedback={onOpenFeedback} />)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-feedback'))
|
|
|
|
|
expect(onOpenFeedback).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
it('displays active vault name', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.getByText('Main Vault')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows fallback "Vault" when vault path does not match', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/unknown/path" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.getByText('Vault')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('opens vault menu on click and shows all vault options', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
|
|
|
|
|
// Click the vault button to open menu
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Work Vault')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calls onSwitchVault when selecting a different vault', () => {
|
|
|
|
|
const onSwitchVault = vi.fn()
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={onSwitchVault} />)
|
|
|
|
|
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
// Click "Work Vault"
|
|
|
|
|
fireEvent.click(screen.getByText('Work Vault'))
|
|
|
|
|
|
|
|
|
|
expect(onSwitchVault).toHaveBeenCalledWith('/Users/luca/Work')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('closes vault menu when clicking outside', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
expect(screen.getByText('Work Vault')).toBeInTheDocument()
|
|
|
|
|
|
|
|
|
|
// Click outside the menu
|
|
|
|
|
fireEvent.mouseDown(document.body)
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByText('Work Vault')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('toggles vault menu open and closed', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
|
|
|
|
|
const vaultButton = screen.getByTitle('Switch vault')
|
|
|
|
|
fireEvent.click(vaultButton)
|
|
|
|
|
expect(screen.getByText('Work Vault')).toBeInTheDocument()
|
|
|
|
|
|
|
|
|
|
// Click again to close
|
|
|
|
|
fireEvent.click(vaultButton)
|
|
|
|
|
expect(screen.queryByText('Work Vault')).not.toBeInTheDocument()
|
|
|
|
|
})
|
2026-02-23 14:45:11 +01:00
|
|
|
|
|
|
|
|
it('shows "Open local folder" option in vault menu', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenLocalFolder={vi.fn()} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
expect(screen.getByText('Open local folder')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calls onOpenLocalFolder when clicking "Open local folder"', () => {
|
|
|
|
|
const onOpenLocalFolder = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenLocalFolder={onOpenLocalFolder} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
fireEvent.click(screen.getByText('Open local folder'))
|
|
|
|
|
expect(onOpenLocalFolder).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-24 00:09:19 +01:00
|
|
|
it('shows add-vault options in vault menu', () => {
|
2026-02-23 14:45:11 +01:00
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
onOpenLocalFolder={vi.fn()}
|
2026-04-12 17:08:07 +02:00
|
|
|
onCloneVault={vi.fn()}
|
2026-02-23 14:45:11 +01:00
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
expect(screen.getByText('Open local folder')).toBeInTheDocument()
|
2026-04-12 17:08:07 +02:00
|
|
|
expect(screen.getByText('Clone Git repo')).toBeInTheDocument()
|
2026-02-23 14:45:11 +01:00
|
|
|
})
|
|
|
|
|
|
2026-04-12 19:17:49 +02:00
|
|
|
it('shows the Getting Started clone action in the vault menu when provided', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
onCloneGettingStarted={vi.fn()}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
expect(screen.getByText('Clone Getting Started Vault')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calls onCloneGettingStarted when clicking the vault menu action', () => {
|
|
|
|
|
const onCloneGettingStarted = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
onCloneGettingStarted={onCloneGettingStarted}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
fireEvent.click(screen.getByText('Clone Getting Started Vault'))
|
|
|
|
|
expect(onCloneGettingStarted).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-30 15:01:57 +02:00
|
|
|
it('shows Changes badge with count when modifiedCount is > 0', () => {
|
2026-02-23 21:15:13 +01:00
|
|
|
render(<StatusBar noteCount={100} modifiedCount={3} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.getByTestId('status-modified-count')).toBeInTheDocument()
|
2026-03-30 15:01:57 +02:00
|
|
|
expect(screen.getByText('Changes')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText('3')).toBeInTheDocument()
|
2026-02-23 21:15:13 +01:00
|
|
|
})
|
|
|
|
|
|
2026-03-30 15:01:57 +02:00
|
|
|
it('does not show Changes badge when modifiedCount is 0', () => {
|
2026-02-23 21:15:13 +01:00
|
|
|
render(<StatusBar noteCount={100} modifiedCount={0} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.queryByTestId('status-modified-count')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-30 15:01:57 +02:00
|
|
|
it('does not show Changes badge when modifiedCount is not provided', () => {
|
2026-02-23 21:15:13 +01:00
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.queryByTestId('status-modified-count')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-23 14:45:11 +01:00
|
|
|
it('closes menu after clicking "Open local folder"', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onOpenLocalFolder={vi.fn()} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTitle('Switch vault'))
|
|
|
|
|
fireEvent.click(screen.getByText('Open local folder'))
|
|
|
|
|
// Menu should close after clicking an action
|
|
|
|
|
expect(screen.queryByText('Open local folder')).not.toBeInTheDocument()
|
|
|
|
|
})
|
2026-02-24 14:46:18 +01:00
|
|
|
|
|
|
|
|
it('calls onClickPending when clicking the pending count', () => {
|
|
|
|
|
const onClickPending = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} modifiedCount={5} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onClickPending={onClickPending} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-modified-count'))
|
|
|
|
|
expect(onClickPending).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('pending count has title for accessibility', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} modifiedCount={3} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onClickPending={vi.fn()} />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByTitle('View pending changes')).toBeInTheDocument()
|
|
|
|
|
})
|
2026-03-03 02:26:41 +01:00
|
|
|
|
2026-03-04 13:11:58 +01:00
|
|
|
it('shows MCP warning badge when status is not_installed', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="not_installed" />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByTestId('status-mcp')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByTitle('MCP server not installed — click to install')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows MCP warning badge when status is no_claude_cli', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="no_claude_cli" />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByTestId('status-mcp')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByTitle('Claude CLI not found — install it first')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('hides MCP badge when status is installed', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="installed" />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.queryByTestId('status-mcp')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('hides MCP badge when status is checking', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="checking" />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.queryByTestId('status-mcp')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('hides MCP badge when no mcpStatus prop provided', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.queryByTestId('status-mcp')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calls onInstallMcp when clicking MCP badge with not_installed status', () => {
|
|
|
|
|
const onInstallMcp = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="not_installed" onInstallMcp={onInstallMcp} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-mcp'))
|
|
|
|
|
expect(onInstallMcp).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does not call onInstallMcp when clicking MCP badge with no_claude_cli status', () => {
|
|
|
|
|
const onInstallMcp = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} mcpStatus="no_claude_cli" onInstallMcp={onInstallMcp} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-mcp'))
|
|
|
|
|
expect(onInstallMcp).not.toHaveBeenCalled()
|
|
|
|
|
})
|
2026-03-07 01:14:12 +01:00
|
|
|
|
2026-03-19 09:51:06 +01:00
|
|
|
it('shows Pull required label when syncStatus is pull_required', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} syncStatus="pull_required" />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByText('Pull required')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-12 19:17:49 +02:00
|
|
|
it('shows an offline chip when offline', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} isOffline={true} />
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByTestId('status-offline')).toHaveTextContent('Offline')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-12 19:38:25 +02:00
|
|
|
it('shows a no-remote chip when the active git vault has no remote', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
remoteStatus={{ branch: 'main', ahead: 0, behind: 0, hasRemote: false }}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByTestId('status-no-remote')).toHaveTextContent('No remote')
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-19 09:51:06 +01:00
|
|
|
it('calls onPullAndPush when clicking Pull required badge', () => {
|
|
|
|
|
const onPullAndPush = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} syncStatus="pull_required" onPullAndPush={onPullAndPush} />
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-sync'))
|
|
|
|
|
expect(onPullAndPush).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows git status popup when clicking idle sync badge', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
syncStatus="idle"
|
|
|
|
|
remoteStatus={{ branch: 'main', ahead: 2, behind: 1, hasRemote: true }}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-sync'))
|
|
|
|
|
expect(screen.getByTestId('git-status-popup')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText('main')).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText(/2 ahead/)).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText(/1 behind/)).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-16 17:03:50 +02:00
|
|
|
it('shows History badge in status bar', () => {
|
2026-03-30 15:01:57 +02:00
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} isGitVault />)
|
|
|
|
|
expect(screen.getByTestId('status-pulse')).toBeInTheDocument()
|
2026-04-16 17:03:50 +02:00
|
|
|
expect(screen.getByText('History')).toBeInTheDocument()
|
2026-03-30 15:01:57 +02:00
|
|
|
})
|
|
|
|
|
|
2026-04-16 17:03:50 +02:00
|
|
|
it('calls onClickPulse when clicking History badge', () => {
|
2026-03-30 15:01:57 +02:00
|
|
|
const onClickPulse = vi.fn()
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} isGitVault onClickPulse={onClickPulse} />)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-pulse'))
|
|
|
|
|
expect(onClickPulse).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-16 17:03:50 +02:00
|
|
|
it('disables History badge when isGitVault is false', () => {
|
2026-03-30 15:01:57 +02:00
|
|
|
const onClickPulse = vi.fn()
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} isGitVault={false} onClickPulse={onClickPulse} />)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-pulse'))
|
|
|
|
|
expect(onClickPulse).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-01 23:17:43 +02:00
|
|
|
it('shows Commit button in status bar', () => {
|
2026-03-30 15:01:57 +02:00
|
|
|
const onCommitPush = vi.fn()
|
|
|
|
|
render(<StatusBar noteCount={100} modifiedCount={5} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onCommitPush={onCommitPush} />)
|
|
|
|
|
expect(screen.getByTestId('status-commit-push')).toBeInTheDocument()
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-commit-push'))
|
|
|
|
|
expect(onCommitPush).toHaveBeenCalledOnce()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-12 19:38:25 +02:00
|
|
|
it('uses a local-only tooltip for the commit button when no remote is configured', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
modifiedCount={5}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
onCommitPush={vi.fn()}
|
|
|
|
|
remoteStatus={{ branch: 'main', ahead: 0, behind: 0, hasRemote: false }}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
expect(screen.getByTestId('status-commit-push')).toHaveAttribute('title', 'Commit locally (no remote configured)')
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-01 23:17:43 +02:00
|
|
|
it('shows Commit button even when no modified files', () => {
|
2026-03-30 15:01:57 +02:00
|
|
|
render(<StatusBar noteCount={100} modifiedCount={0} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} onCommitPush={vi.fn()} />)
|
2026-04-01 23:17:43 +02:00
|
|
|
expect(screen.getByTestId('status-commit-push')).toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('hides Commit button when no onCommitPush callback', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} modifiedCount={5} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
2026-03-30 15:01:57 +02:00
|
|
|
expect(screen.queryByTestId('status-commit-push')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-04 16:31:05 +02:00
|
|
|
it('shows Claude Code badge when installed', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} claudeCodeStatus="installed" claudeCodeVersion="1.0.20" />)
|
|
|
|
|
const badge = screen.getByTestId('status-claude-code')
|
|
|
|
|
expect(badge).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText('Claude Code')).toBeInTheDocument()
|
|
|
|
|
expect(badge.getAttribute('title')).toBe('Claude Code 1.0.20')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows Claude Code missing badge with warning when missing', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} claudeCodeStatus="missing" />)
|
|
|
|
|
const badge = screen.getByTestId('status-claude-code')
|
|
|
|
|
expect(badge).toBeInTheDocument()
|
|
|
|
|
expect(screen.getByText('Claude Code missing')).toBeInTheDocument()
|
|
|
|
|
expect(badge.getAttribute('title')).toBe('Claude Code not found — click to install')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('opens install URL when clicking missing Claude Code badge', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} claudeCodeStatus="missing" />)
|
|
|
|
|
fireEvent.click(screen.getByTestId('status-claude-code'))
|
|
|
|
|
expect(openExternalUrl).toHaveBeenCalledWith('https://docs.anthropic.com/en/docs/claude-code')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('opens install URL on Enter key for missing Claude Code badge', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} claudeCodeStatus="missing" />)
|
|
|
|
|
fireEvent.keyDown(screen.getByTestId('status-claude-code'), { key: 'Enter' })
|
|
|
|
|
expect(openExternalUrl).toHaveBeenCalledWith('https://docs.anthropic.com/en/docs/claude-code')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('hides Claude Code badge when status is checking', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} claudeCodeStatus="checking" />)
|
|
|
|
|
expect(screen.queryByTestId('status-claude-code')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('hides Claude Code badge when no claudeCodeStatus prop provided', () => {
|
|
|
|
|
render(<StatusBar noteCount={100} vaultPath="/Users/luca/Laputa" vaults={vaults} onSwitchVault={vi.fn()} />)
|
|
|
|
|
expect(screen.queryByTestId('status-claude-code')).not.toBeInTheDocument()
|
|
|
|
|
})
|
|
|
|
|
|
2026-04-14 11:55:48 +02:00
|
|
|
it('shows the active AI agent directly in the bottom bar', () => {
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
aiAgentsStatus={installedAiAgentsStatus}
|
|
|
|
|
defaultAiAgent="claude_code"
|
|
|
|
|
onSetDefaultAiAgent={vi.fn()}
|
|
|
|
|
/>,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(screen.getByTestId('status-ai-agents')).toHaveTextContent('AI: Claude')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('opens the AI agent switcher from the keyboard and switches agents', () => {
|
|
|
|
|
const onSetDefaultAiAgent = vi.fn()
|
|
|
|
|
render(
|
|
|
|
|
<StatusBar
|
|
|
|
|
noteCount={100}
|
|
|
|
|
vaultPath="/Users/luca/Laputa"
|
|
|
|
|
vaults={vaults}
|
|
|
|
|
onSwitchVault={vi.fn()}
|
|
|
|
|
aiAgentsStatus={installedAiAgentsStatus}
|
|
|
|
|
defaultAiAgent="claude_code"
|
|
|
|
|
onSetDefaultAiAgent={onSetDefaultAiAgent}
|
|
|
|
|
/>,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const trigger = screen.getByTestId('status-ai-agents')
|
|
|
|
|
trigger.focus()
|
|
|
|
|
act(() => {
|
|
|
|
|
fireEvent.keyDown(trigger, { key: 'ArrowDown' })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const codexOption = screen.getByRole('menuitemradio', { name: /Codex/ })
|
|
|
|
|
codexOption.focus()
|
|
|
|
|
act(() => {
|
|
|
|
|
fireEvent.keyDown(codexOption, { key: 'Enter' })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(onSetDefaultAiAgent).toHaveBeenCalledWith('codex')
|
|
|
|
|
})
|
|
|
|
|
|
test: add component tests — StatusBar, CommitDialog, DiffView, QuickOpenPalette, ResizeHandle, CreateNoteDialog, EditableValue, TypeCustomizePopover, DynamicPropertiesPanel, InspectorPanels
251 tests covering:
- StatusBar: note count, vault menu interactions
- CommitDialog: file count badge, submit via click/Cmd+Enter, validation
- DiffView: line rendering, color coding, line numbers
- QuickOpenPalette: search, sorting, fuzzy filtering, keyboard navigation
- ResizeHandle: drag with rAF batching, delta accumulation, cursor reset
- CreateNoteDialog: title input, submit, validation, defaultType
- EditableValue: view/edit modes, Enter/Escape/blur, TagPillList CRUD
- TypeCustomizePopover: resolveIcon, color/icon selection
- DynamicPropertiesPanel: properties, status pills, boolean toggle, tags,
add property form, value coercion, delete
- InspectorPanels: relationships, backlinks, git history, navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:44 +01:00
|
|
|
})
|