2026-02-27 14:41:23 +01:00
import { describe , it , expect , vi , beforeEach } from 'vitest'
2026-05-11 16:37:06 +02:00
import { act , render , screen , fireEvent , within } from '@testing-library/react'
2026-04-19 16:18:35 +02:00
import { TooltipProvider } from '@/components/ui/tooltip'
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'
2026-04-19 16:18:35 +02:00
import { StatusBarPrimarySection } from './status-bar/StatusBarSections'
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 type { VaultOption } from './StatusBar'
2026-02-27 14:41:23 +01:00
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 [ ] = [
2026-05-11 16:37:06 +02:00
{ label : 'Main Vault' , path : '/Users/luca/Laputa' , alias : 'main' , mounted : true } ,
{ label : 'Work Vault' , path : '/Users/luca/Work' , alias : 'work' , mounted : false } ,
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-04-25 21:59:10 +02:00
const DEFAULT_WINDOW_WIDTH = 1280
function setWindowWidth ( width : number ) {
Object . defineProperty ( window , 'innerWidth' , {
configurable : true ,
writable : true ,
value : width ,
} )
}
function renderDenseStatusBar() {
return render (
< StatusBar
noteCount = { 100 }
modifiedCount = { 5 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
remoteStatus = { { branch : 'main' , ahead : 0 , behind : 0 , hasRemote : false } }
onCommitPush = { vi . fn ( ) }
onClickPulse = { vi . fn ( ) }
onOpenFeedback = { vi . fn ( ) }
buildNumber = "b281"
onCheckForUpdates = { vi . fn ( ) }
mcpStatus = "not_installed"
/ >
)
}
2026-04-17 02:34:41 +02:00
async function expectTooltip ( trigger : HTMLElement , . . . parts : string [ ] ) {
act ( ( ) = > {
fireEvent . focus ( trigger )
} )
const tooltip = await screen . findByRole ( 'tooltip' )
for ( const part of parts ) {
expect ( tooltip ) . toHaveTextContent ( part )
}
act ( ( ) = > {
fireEvent . blur ( trigger )
} )
}
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-25 21:59:10 +02:00
setWindowWidth ( DEFAULT_WINDOW_WIDTH )
2026-02-27 14:41:23 +01:00
} )
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-04-27 23:54:50 +02:00
it ( 'shows the vault reload badge while a reload is active' , ( ) = > {
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } isVaultReloading / > )
expect ( screen . getByTestId ( 'status-vault-reloading' ) ) . toHaveAccessibleName ( 'Reloading vault from disk' )
} )
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 ( )
} )
2026-04-17 02:34:41 +02:00
it ( 'build number shows the update tooltip on focus' , async ( ) = > {
2026-03-03 19:46:37 +01:00
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } buildNumber = "b281" onCheckForUpdates = { vi . fn ( ) } / > )
2026-04-17 02:34:41 +02:00
await expectTooltip ( screen . getByRole ( 'button' , { name : 'Check for updates' } ) , 'Check for updates' )
2026-04-30 21:01:09 +02:00
} , 10 _000 )
2026-03-03 19:46:37 +01:00
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-24 17:45:43 +02:00
it ( 'shows Contribute button when callback is provided' , ( ) = > {
2026-04-10 12:45:37 +02:00
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } onOpenFeedback = { vi . fn ( ) } / > )
expect ( screen . getByTestId ( 'status-feedback' ) ) . toBeInTheDocument ( )
2026-04-24 17:45:43 +02:00
expect ( screen . getByText ( 'Contribute' ) ) . toBeInTheDocument ( )
2026-04-10 12:45:37 +02:00
} )
2026-04-24 17:45:43 +02:00
it ( 'calls onOpenFeedback when Contribute is clicked' , ( ) = > {
2026-04-10 12:45:37 +02:00
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 ( )
} )
2026-05-06 22:57:06 +02:00
it ( 'shows and opens Docs from the bottom bar' , ( ) = > {
const onOpenDocs = vi . fn ( )
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } onOpenDocs = { onOpenDocs } / > )
expect ( screen . getByTestId ( 'status-docs' ) ) . toHaveTextContent ( 'Docs' )
fireEvent . click ( screen . getByTestId ( 'status-docs' ) )
expect ( onOpenDocs ) . toHaveBeenCalledOnce ( )
} )
2026-04-24 23:58:26 +02:00
it ( 'shows a theme toggle instead of the notifications placeholder' , ( ) = > {
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
themeMode = "light"
onToggleThemeMode = { vi . fn ( ) }
/ > ,
)
expect ( screen . getByTestId ( 'status-theme-mode' ) ) . toHaveAccessibleName ( 'Switch to dark mode' )
expect ( screen . queryByLabelText ( 'Notifications are coming soon' ) ) . not . toBeInTheDocument ( )
} )
2026-04-27 02:20:28 +02:00
it ( 'end-aligns the theme tooltip to keep it inside the right window edge' , async ( ) = > {
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
themeMode = "light"
onToggleThemeMode = { vi . fn ( ) }
onOpenSettings = { vi . fn ( ) }
/ > ,
)
act ( ( ) = > {
fireEvent . focus ( screen . getByTestId ( 'status-theme-mode' ) )
} )
const tooltip = await screen . findByTestId ( 'status-theme-mode-tooltip' )
expect ( tooltip ) . toHaveAttribute ( 'data-align' , 'end' )
expect ( tooltip ) . toHaveTextContent ( 'Switch to dark mode' )
} )
2026-04-24 23:58:26 +02:00
it ( 'calls onToggleThemeMode from the bottom bar' , ( ) = > {
const onToggleThemeMode = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
themeMode = "dark"
onToggleThemeMode = { onToggleThemeMode }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch to light mode' } ) )
expect ( onToggleThemeMode ) . 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
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
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
expect ( screen . getByText ( 'Work Vault' ) ) . toBeInTheDocument ( )
} )
2026-05-11 16:37:06 +02:00
it ( 'does not show workspace management or mount controls before opt-in' , ( ) = > {
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
onUpdateWorkspaceIdentity = { vi . fn ( ) }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
expect ( screen . queryByTestId ( 'vault-menu-manage-vaults' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByRole ( 'checkbox' ) ) . not . toBeInTheDocument ( )
} )
it ( 'mounts and unmounts workspaces from the vault menu after opt-in' , ( ) = > {
const onSwitchVault = vi . fn ( )
const onUpdateWorkspaceIdentity = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
multiWorkspaceEnabled = { true }
onSwitchVault = { onSwitchVault }
onUpdateWorkspaceIdentity = { onUpdateWorkspaceIdentity }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
fireEvent . click ( screen . getByRole ( 'checkbox' , { name : 'Include Work Vault in the unified graph' } ) )
expect ( onUpdateWorkspaceIdentity ) . toHaveBeenCalledWith ( '/Users/luca/Work' , { mounted : true } )
expect ( onSwitchVault ) . not . toHaveBeenCalled ( )
expect ( screen . getByText ( 'Work Vault' ) ) . toBeInTheDocument ( )
} )
2026-05-15 11:18:51 +02:00
it ( 'shows the active workspace real mount state so stale unmounted defaults can be repaired' , ( ) = > {
2026-05-11 16:37:06 +02:00
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { [
{ . . . vaults [ 0 ] , mounted : false } ,
vaults [ 1 ] ,
] }
multiWorkspaceEnabled = { true }
onSwitchVault = { vi . fn ( ) }
onUpdateWorkspaceIdentity = { vi . fn ( ) }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
const activeCheckbox = screen . getByRole ( 'checkbox' , { name : 'Include Main Vault in the unified graph' } )
2026-05-15 11:18:51 +02:00
expect ( activeCheckbox ) . not . toBeChecked ( )
expect ( activeCheckbox ) . not . toBeDisabled ( )
2026-05-11 16:37:06 +02:00
} )
it ( 'uses the expanded multi-workspace vault picker layout' , ( ) = > {
const onOpenVaultSettings = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { [
{ . . . vaults [ 0 ] , color : 'purple' } ,
{ . . . vaults [ 1 ] , color : 'green' } ,
] }
multiWorkspaceEnabled = { true }
onSwitchVault = { vi . fn ( ) }
onOpenVaultSettings = { onOpenVaultSettings }
onCreateEmptyVault = { vi . fn ( ) }
onUpdateWorkspaceIdentity = { vi . fn ( ) }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
expect ( screen . getByTestId ( 'vault-menu-popover' ) ) . toHaveStyle ( { minWidth : '320px' } )
expect ( screen . getByText ( 'Available vaults' ) ) . toBeInTheDocument ( )
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Manage vaults' } ) )
expect ( onOpenVaultSettings ) . toHaveBeenCalledOnce ( )
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
const activeItem = screen . getByTestId ( 'vault-menu-item-Main Vault' )
const defaultLabel = within ( activeItem ) . getByTestId ( 'vault-menu-default-label' )
const activeBadge = within ( activeItem ) . getByTestId ( 'vault-menu-workspace-badge-Main Vault' )
expect ( within ( activeItem ) . getByTestId ( 'vault-menu-item-label-Main Vault' ) . className ) . toContain ( 'text-[12px]' )
expect ( within ( activeItem ) . getByTestId ( 'vault-menu-item-label-Main Vault' ) . getAttribute ( 'style' ) ) . toContain ( 'background: transparent' )
expect ( defaultLabel ) . toHaveTextContent ( 'Default' )
expect ( activeBadge ) . toHaveTextContent ( 'MV' )
expect ( defaultLabel . compareDocumentPosition ( activeBadge ) & Node . DOCUMENT_POSITION_FOLLOWING ) . toBeTruthy ( )
const workBadge = screen . getByTestId ( 'vault-menu-workspace-badge-Work Vault' )
expect ( workBadge ) . toHaveTextContent ( 'WV' )
expect ( workBadge . getAttribute ( 'style' ) ) . toContain ( 'border-color: var(--accent-green)' )
const createAction = screen . getByTestId ( 'vault-menu-create-empty' )
expect ( createAction . className ) . toContain ( 'text-[12px]' )
expect ( createAction . getAttribute ( 'style' ) ) . toContain ( 'color: var(--muted-foreground)' )
} )
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 ( 'calls onSwitchVault when selecting a different vault' , ( ) = > {
const onSwitchVault = vi . fn ( )
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { onSwitchVault } / > )
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
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
// Click "Work Vault"
fireEvent . click ( screen . getByText ( 'Work Vault' ) )
expect ( onSwitchVault ) . toHaveBeenCalledWith ( '/Users/luca/Work' )
} )
2026-05-11 16:37:06 +02:00
it ( 'sets the default workspace instead of switching vaults after multi-workspace opt-in' , ( ) = > {
const onSetDefaultWorkspace = vi . fn ( )
const onSwitchVault = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
defaultWorkspacePath = "/Users/luca/Laputa"
vaults = { vaults }
multiWorkspaceEnabled = { true }
onSwitchVault = { onSwitchVault }
onSetDefaultWorkspace = { onSetDefaultWorkspace }
onUpdateWorkspaceIdentity = { vi . fn ( ) }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
fireEvent . click ( screen . getByText ( 'Work Vault' ) )
expect ( onSetDefaultWorkspace ) . toHaveBeenCalledWith ( '/Users/luca/Work' )
expect ( onSwitchVault ) . not . toHaveBeenCalled ( )
} )
it ( 'unmounts the current default by moving the default to another included workspace first' , ( ) = > {
const onSetDefaultWorkspace = vi . fn ( )
const onUpdateWorkspaceIdentity = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
defaultWorkspacePath = "/Users/luca/Laputa"
vaults = { [
{ . . . vaults [ 0 ] , mounted : true } ,
{ . . . vaults [ 1 ] , mounted : true } ,
] }
multiWorkspaceEnabled = { true }
onSwitchVault = { vi . fn ( ) }
onSetDefaultWorkspace = { onSetDefaultWorkspace }
onUpdateWorkspaceIdentity = { onUpdateWorkspaceIdentity }
/ > ,
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
fireEvent . click ( screen . getByRole ( 'checkbox' , { name : 'Include Main Vault in the unified graph' } ) )
expect ( onSetDefaultWorkspace ) . toHaveBeenCalledWith ( '/Users/luca/Work' )
expect ( onUpdateWorkspaceIdentity ) . toHaveBeenCalledWith ( '/Users/luca/Laputa' , { mounted : false } )
} )
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 ( 'closes vault menu when clicking outside' , ( ) = > {
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } / > )
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
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
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 ( ) } / > )
2026-04-17 02:34:41 +02:00
const vaultButton = screen . getByRole ( 'button' , { name : 'Switch vault' } )
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
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 ( ) } / >
)
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-02-23 14:45:11 +01:00
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 } / >
)
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-02-23 14:45:11 +01:00
fireEvent . click ( screen . getByText ( 'Open local folder' ) )
expect ( onOpenLocalFolder ) . toHaveBeenCalledOnce ( )
} )
2026-04-19 01:06:40 +02:00
it ( 'shows "Create empty vault" option in vault menu' , ( ) = > {
render (
< StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } onCreateEmptyVault = { vi . fn ( ) } / >
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
expect ( screen . getByText ( 'Create empty vault' ) ) . toBeInTheDocument ( )
} )
it ( 'calls onCreateEmptyVault when clicking "Create empty vault"' , ( ) = > {
const onCreateEmptyVault = vi . fn ( )
render (
< StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } onCreateEmptyVault = { onCreateEmptyVault } / >
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
fireEvent . click ( screen . getByText ( 'Create empty vault' ) )
expect ( onCreateEmptyVault ) . 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 ( ) }
2026-04-19 01:06:40 +02:00
onCreateEmptyVault = { vi . fn ( ) }
2026-02-23 14:45:11 +01:00
onOpenLocalFolder = { vi . fn ( ) }
2026-04-12 17:08:07 +02:00
onCloneVault = { vi . fn ( ) }
2026-02-23 14:45:11 +01:00
/ >
)
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-04-19 01:06:40 +02:00
expect ( screen . getByText ( 'Create empty vault' ) ) . toBeInTheDocument ( )
2026-02-23 14:45:11 +01:00
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 ( ) }
/ >
)
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-04-12 19:17:49 +02:00
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 }
/ >
)
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-04-12 19:17:49 +02:00
fireEvent . click ( screen . getByText ( 'Clone Getting Started Vault' ) )
expect ( onCloneGettingStarted ) . toHaveBeenCalledOnce ( )
} )
2026-04-20 12:19:10 +02:00
it ( 'exposes an in-row, hover-revealed remove action for non-active vaults' , ( ) = > {
2026-04-19 15:15:31 +02:00
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
onRemoveVault = { vi . fn ( ) }
/ >
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-04-20 12:19:10 +02:00
const item = screen . getByTestId ( 'vault-menu-item-Work Vault' )
const removeAction = screen . getByTestId ( 'vault-menu-remove-Work Vault' )
expect ( item . className ) . toContain ( 'hover:bg-[var(--hover)]' )
2026-05-11 16:37:06 +02:00
expect ( removeAction . compareDocumentPosition ( within ( item ) . getByText ( 'Work Vault' ) ) & Node . DOCUMENT_POSITION_PRECEDING ) . toBeTruthy ( )
expect ( removeAction . className ) . not . toContain ( 'absolute' )
expect ( removeAction . className ) . not . toContain ( 'right-1' )
2026-04-20 12:19:10 +02:00
expect ( removeAction . className ) . toContain ( 'group-hover:opacity-100' )
expect ( removeAction . className ) . toContain ( 'group-focus-within:opacity-100' )
expect ( removeAction . className ) . toContain ( 'pointer-events-none' )
2026-04-19 15:15:31 +02:00
expect ( screen . getByRole ( 'button' , { name : 'Remove Work Vault from list' } ) ) . toBeInTheDocument ( )
} )
2026-05-11 16:37:06 +02:00
it ( 'confirms before removing a vault from the vault menu' , ( ) = > {
2026-04-19 15:15:31 +02:00
const onRemoveVault = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
onRemoveVault = { onRemoveVault }
/ >
)
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Remove Work Vault from list' } ) )
2026-05-11 16:37:06 +02:00
expect ( onRemoveVault ) . not . toHaveBeenCalled ( )
expect ( screen . getByTestId ( 'confirm-delete-dialog' ) ) . toBeInTheDocument ( )
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Remove vault' } ) )
2026-04-19 15:15:31 +02:00
expect ( onRemoveVault ) . toHaveBeenCalledWith ( '/Users/luca/Work' )
} )
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-04-26 12:12:05 +02:00
it ( 'keeps the bottom bar compact and unwrapped at medium widths' , ( ) = > {
2026-04-25 21:59:10 +02:00
setWindowWidth ( 980 )
renderDenseStatusBar ( )
expect ( screen . getByTestId ( 'status-bar' ) ) . toHaveStyle ( {
2026-04-26 12:12:05 +02:00
flexWrap : 'nowrap' ,
height : '30px' ,
2026-04-25 21:59:10 +02:00
} )
2026-04-26 12:12:05 +02:00
expect ( screen . getByTestId ( 'status-commit-push' ) ) . toBeInTheDocument ( )
expect ( screen . getByTestId ( 'status-pulse' ) ) . toBeInTheDocument ( )
expect ( screen . getByTestId ( 'status-feedback' ) ) . toBeInTheDocument ( )
expect ( screen . queryByText ( 'Commit' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'History' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'Contribute' ) ) . not . toBeInTheDocument ( )
2026-04-25 21:59:10 +02:00
} )
it ( 'collapses status labels to icon-first controls at very narrow widths' , ( ) = > {
2026-05-24 11:04:18 +02:00
setWindowWidth ( 920 )
2026-04-25 21:59:10 +02:00
renderDenseStatusBar ( )
expect ( screen . getByTestId ( 'status-bar' ) ) . toHaveStyle ( {
2026-04-26 10:33:16 +02:00
flexWrap : 'nowrap' ,
height : '30px' ,
2026-04-25 21:59:10 +02:00
} )
expect ( screen . getByTestId ( 'status-commit-push' ) ) . toBeInTheDocument ( )
expect ( screen . getByTestId ( 'status-pulse' ) ) . toBeInTheDocument ( )
expect ( screen . getByTestId ( 'status-feedback' ) ) . toBeInTheDocument ( )
2026-04-26 10:33:16 +02:00
expect ( screen . getByTestId ( 'status-build-number' ) ) . toBeInTheDocument ( )
2026-05-29 17:21:23 +02:00
expect ( screen . queryByTestId ( 'status-claude-code' ) ) . not . toBeInTheDocument ( )
2026-04-25 21:59:10 +02:00
expect ( screen . queryByText ( 'Commit' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'History' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'Contribute' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'No remote' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'MCP' ) ) . not . toBeInTheDocument ( )
2026-04-26 10:33:16 +02:00
expect ( screen . queryByText ( 'b281' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByText ( 'Claude Code missing' ) ) . not . toBeInTheDocument ( )
} )
2026-05-24 11:04:18 +02:00
it ( 'stacks the footer into two rows once the narrow-width breakpoint is crossed' , ( ) = > {
setWindowWidth ( 900 )
renderDenseStatusBar ( )
expect ( screen . getByTestId ( 'status-bar' ) ) . toHaveStyle ( {
flexWrap : 'wrap' ,
height : 'auto' ,
} )
expect ( screen . getByTestId ( 'status-commit-push' ) ) . toBeInTheDocument ( )
expect ( screen . getByTestId ( 'status-pulse' ) ) . toBeInTheDocument ( )
expect ( screen . getByTestId ( 'status-feedback' ) ) . toBeInTheDocument ( )
} )
2026-05-29 17:21:23 +02:00
it ( 'does not render the legacy AI agent control in the status bar' , ( ) = > {
2026-05-24 11:04:18 +02:00
setWindowWidth ( 920 )
2026-04-26 10:33:16 +02:00
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
/ >
)
2026-05-29 17:21:23 +02:00
expect ( screen . queryByTestId ( 'status-ai-agents' ) ) . not . toBeInTheDocument ( )
2026-04-26 10:33:16 +02:00
expect ( screen . queryByText ( 'Claude' ) ) . not . toBeInTheDocument ( )
2026-04-25 21:59:10 +02: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 ( ) } / >
)
2026-04-17 02:34:41 +02:00
fireEvent . click ( screen . getByRole ( 'button' , { name : 'Switch vault' } ) )
2026-02-23 14:45:11 +01:00
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 ( )
} )
2026-04-17 02:34:41 +02:00
it ( 'pending changes tooltip is available on keyboard focus' , async ( ) = > {
2026-02-24 14:46:18 +01:00
render (
< StatusBar noteCount = { 100 } modifiedCount = { 3 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } onClickPending = { vi . fn ( ) } / >
)
2026-04-17 02:34:41 +02:00
await expectTooltip ( screen . getByRole ( 'button' , { name : 'View pending changes' } ) , 'View pending changes' )
2026-02-24 14:46:18 +01:00
} )
2026-03-03 02:26:41 +01:00
2026-04-17 02:34:41 +02:00
it ( 'shows MCP warning badge when status is not_installed' , async ( ) = > {
2026-03-04 13:11:58 +01:00
render (
< StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } mcpStatus = "not_installed" / >
)
expect ( screen . getByTestId ( 'status-mcp' ) ) . toBeInTheDocument ( )
2026-04-22 19:18:52 +02:00
await expectTooltip ( screen . getByRole ( 'button' , { name : 'External AI tools not connected — click to set up' } ) , 'External AI tools not connected — click to set up' )
2026-03-04 13:11:58 +01:00
} )
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 ( )
} )
2026-05-13 10:29:22 +02:00
it ( 'hides MCP badge when AI features are disabled' , ( ) = > {
render (
< StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } aiFeaturesEnabled = { false } mcpStatus = "not_installed" / >
)
expect ( screen . queryByTestId ( 'status-mcp' ) ) . not . toBeInTheDocument ( )
} )
2026-03-04 13:11:58 +01:00
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 ( )
} )
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-04-19 16:18:35 +02:00
it ( 'opens the add-remote flow when clicking the no-remote chip' , ( ) = > {
const onAddRemote = vi . fn ( )
render (
< TooltipProvider >
< StatusBarPrimarySection
modifiedCount = { 0 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
onAddRemote = { onAddRemote }
syncStatus = "idle"
lastSyncTime = { null }
conflictCount = { 0 }
remoteStatus = { { branch : 'main' , ahead : 0 , behind : 0 , hasRemote : false } }
/ >
< / TooltipProvider >
)
fireEvent . click ( screen . getByTestId ( 'status-no-remote' ) )
expect ( onAddRemote ) . toHaveBeenCalledOnce ( )
} )
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' ) )
2026-05-03 20:25:50 +02:00
expect ( screen . getByTestId ( 'status-bar' ) ) . toHaveStyle ( { zIndex : '30' } )
2026-03-19 09:51:06 +01:00
expect ( screen . getByTestId ( 'git-status-popup' ) ) . toBeInTheDocument ( )
2026-05-24 13:11:29 +02:00
expect ( screen . queryByText ( 'main' ) ) . not . toBeInTheDocument ( )
2026-03-19 09:51:06 +01:00
expect ( screen . getByText ( /2 ahead/ ) ) . toBeInTheDocument ( )
expect ( screen . getByText ( /1 behind/ ) ) . toBeInTheDocument ( )
2026-05-23 13:36:39 +02:00
} )
2026-05-24 13:11:29 +02:00
it ( 'keeps sync controls vault-agnostic when multiple vaults are active' , ( ) = > {
2026-05-23 13:36:39 +02:00
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
multiWorkspaceEnabled = { true }
onSwitchVault = { vi . fn ( ) }
repositories = { [
{ path : '/Users/luca/Laputa' , label : 'Main Vault' , defaultForNewNotes : true } ,
{ path : '/Users/luca/Work' , label : 'Work Vault' , defaultForNewNotes : false } ,
] }
selectedRepositoryPath = "/Users/luca/Work"
onRepositoryChange = { vi . fn ( ) }
syncStatus = "idle"
remoteStatus = { { branch : 'main' , ahead : 0 , behind : 0 , hasRemote : true } }
/ >
)
2026-05-24 13:11:29 +02:00
expect ( screen . getByTestId ( 'status-sync' ) ) . not . toHaveTextContent ( 'Work Vault' )
expect ( screen . getByTestId ( 'status-sync' ) ) . toHaveTextContent ( 'Not synced' )
2026-05-23 13:36:39 +02:00
fireEvent . click ( screen . getByTestId ( 'status-sync' ) )
2026-05-24 13:11:29 +02:00
expect ( screen . queryByTestId ( 'git-status-repository-select' ) ) . not . toBeInTheDocument ( )
2026-03-19 09:51:06 +01:00
} )
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-26 17:08:14 +02:00
it ( 'replaces git controls with a missing-Git warning when isGitVault is false' , ( ) = > {
render (
< StatusBar
noteCount = { 100 }
modifiedCount = { 5 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
isGitVault = { false }
onClickPulse = { vi . fn ( ) }
onCommitPush = { vi . fn ( ) }
/ >
)
expect ( screen . getByTestId ( 'status-missing-git' ) ) . toBeInTheDocument ( )
expect ( screen . getByText ( 'Git disabled' ) ) . toBeInTheDocument ( )
expect ( screen . queryByTestId ( 'status-pulse' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByTestId ( 'status-commit-push' ) ) . not . toBeInTheDocument ( )
} )
2026-05-16 12:13:20 +02:00
it ( 'hides all git controls when Git features are disabled globally' , ( ) = > {
render (
< StatusBar
noteCount = { 100 }
modifiedCount = { 5 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
gitFeaturesEnabled = { false }
isGitVault = { false }
onInitializeGit = { vi . fn ( ) }
onClickPulse = { vi . fn ( ) }
onCommitPush = { vi . fn ( ) }
/ >
)
expect ( screen . queryByTestId ( 'status-missing-git' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByTestId ( 'status-pulse' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByTestId ( 'status-commit-push' ) ) . not . toBeInTheDocument ( )
expect ( screen . queryByTestId ( 'status-changes' ) ) . not . toBeInTheDocument ( )
} )
2026-04-26 17:08:14 +02:00
it ( 'opens Git setup from the missing-Git warning with mouse and keyboard' , ( ) = > {
const onInitializeGit = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
isGitVault = { false }
onInitializeGit = { onInitializeGit }
/ >
)
const warning = screen . getByTestId ( 'status-missing-git' )
fireEvent . click ( warning )
expect ( onInitializeGit ) . toHaveBeenCalledOnce ( )
warning . focus ( )
fireEvent . keyDown ( warning , { key : 'Enter' } )
expect ( onInitializeGit ) . toHaveBeenCalledTimes ( 2 )
2026-03-30 15:01:57 +02:00
} )
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-16 23:38:30 +02:00
it ( 'activates the Commit button with the keyboard' , ( ) = > {
const onCommitPush = vi . fn ( )
render ( < StatusBar noteCount = { 100 } modifiedCount = { 5 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } onCommitPush = { onCommitPush } / > )
const commitButton = screen . getByTestId ( 'status-commit-push' )
commitButton . focus ( )
fireEvent . keyDown ( commitButton , { key : 'Enter' } )
expect ( onCommitPush ) . toHaveBeenCalledOnce ( )
} )
2026-05-12 16:14:22 +02:00
it ( 'shows Commit progress feedback and blocks duplicate activation while pending' , ( ) = > {
const onCommitPush = vi . fn ( )
render (
< StatusBar
noteCount = { 100 }
modifiedCount = { 5 }
vaultPath = "/Users/luca/Laputa"
vaults = { vaults }
onSwitchVault = { vi . fn ( ) }
onCommitPush = { onCommitPush }
commitActionPending
/ >
)
const commitButton = screen . getByTestId ( 'status-commit-push' )
expect ( commitButton ) . toHaveAttribute ( 'aria-busy' , 'true' )
expect ( commitButton ) . toHaveAttribute ( 'aria-disabled' , 'true' )
expect ( commitButton . querySelector ( '.animate-spin' ) ) . not . toBeNull ( )
fireEvent . click ( commitButton )
fireEvent . keyDown ( commitButton , { key : 'Enter' } )
fireEvent . keyDown ( commitButton , { key : ' ' } )
expect ( onCommitPush ) . not . toHaveBeenCalled ( )
} )
2026-04-17 02:34:41 +02:00
it ( 'uses a local-only tooltip for the commit button when no remote is configured' , async ( ) = > {
2026-04-12 19:38:25 +02:00
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 } }
/ >
)
2026-04-17 02:34:41 +02:00
await expectTooltip ( screen . getByRole ( 'button' , { name : 'Commit changes locally' } ) , 'Commit changes locally' )
2026-04-12 19:38:25 +02:00
} )
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-05-29 17:21:23 +02:00
it ( 'does not render the legacy Claude Code badge in the status bar' , ( ) = > {
2026-04-04 16:31:05 +02:00
render ( < StatusBar noteCount = { 100 } vaultPath = "/Users/luca/Laputa" vaults = { vaults } onSwitchVault = { vi . fn ( ) } / > )
expect ( screen . queryByTestId ( 'status-claude-code' ) ) . 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
} )