diff --git a/src/components/Editor.test.tsx b/src/components/Editor.test.tsx
new file mode 100644
index 00000000..24165354
--- /dev/null
+++ b/src/components/Editor.test.tsx
@@ -0,0 +1,220 @@
+import { render, screen, fireEvent } from '@testing-library/react'
+import { describe, it, expect, vi } from 'vitest'
+
+// Mock BlockNote components
+vi.mock('@blocknote/core', () => ({
+ BlockNoteSchema: { create: () => ({}) },
+ defaultInlineContentSpecs: {},
+ filterSuggestionItems: vi.fn(() => []),
+}))
+
+vi.mock('@blocknote/core/extensions', () => ({
+ filterSuggestionItems: vi.fn(() => []),
+}))
+
+vi.mock('@blocknote/react', () => ({
+ createReactInlineContentSpec: () => ({ render: () => null }),
+ useCreateBlockNote: () => ({
+ tryParseMarkdownToBlocks: async () => [],
+ replaceBlocks: () => {},
+ document: [],
+ insertInlineContent: () => {},
+ }),
+ SuggestionMenuController: () => null,
+}))
+
+vi.mock('@blocknote/mantine', () => ({
+ BlockNoteView: ({ children }: any) =>
{children}
,
+}))
+
+vi.mock('@blocknote/mantine/style.css', () => ({}))
+
+import { Editor } from './Editor'
+import type { VaultEntry } from '../types'
+
+const mockEntry: VaultEntry = {
+ path: '/vault/project/test.md',
+ filename: 'test.md',
+ title: 'Test Project',
+ isA: 'Project',
+ aliases: [],
+ belongsTo: [],
+ relatedTo: [],
+ status: 'Active',
+ owner: 'Luca',
+ cadence: null,
+ modifiedAt: 1700000000,
+ createdAt: null,
+ fileSize: 1024,
+}
+
+const mockContent = `---
+title: Test Project
+is_a: Project
+Status: Active
+---
+
+# Test Project
+
+This is a test note with some words to count.
+`
+
+const mockTab = { entry: mockEntry, content: mockContent }
+
+const defaultProps = {
+ tabs: [] as { entry: VaultEntry; content: string }[],
+ activeTabPath: null as string | null,
+ entries: [mockEntry],
+ onSwitchTab: vi.fn(),
+ onCloseTab: vi.fn(),
+ onNavigateWikilink: vi.fn(),
+ inspectorCollapsed: true,
+ onToggleInspector: vi.fn(),
+ inspectorWidth: 280,
+ onInspectorResize: vi.fn(),
+ inspectorEntry: null as VaultEntry | null,
+ inspectorContent: null as string | null,
+ allContent: {} as Record,
+ gitHistory: [],
+ onCreateNote: vi.fn(),
+}
+
+describe('Editor', () => {
+ it('shows empty state when no tabs are open', () => {
+ render()
+ expect(screen.getByText('Select a note to start editing')).toBeInTheDocument()
+ expect(screen.getByText(/Cmd\+P to search/)).toBeInTheDocument()
+ })
+
+ it('renders tab bar with open tabs', () => {
+ render(
+
+ )
+ expect(screen.getAllByText('Test Project').length).toBeGreaterThan(0)
+ })
+
+ it('renders breadcrumb bar with note info', () => {
+ render(
+
+ )
+ // Breadcrumb shows type and title
+ expect(screen.getByText('Project')).toBeInTheDocument()
+ // Word count shown
+ expect(screen.getByText(/words/)).toBeInTheDocument()
+ })
+
+ it('calls onCloseTab when close button is clicked', () => {
+ const onCloseTab = vi.fn()
+ render(
+
+ )
+ // Find the close button (X icon) in the tab
+ const closeButtons = document.querySelectorAll('button')
+ const tabCloseBtn = Array.from(closeButtons).find(btn => {
+ const svg = btn.querySelector('svg')
+ return svg && btn.closest('[class*="group"]')
+ })
+ if (tabCloseBtn) {
+ fireEvent.click(tabCloseBtn)
+ expect(onCloseTab).toHaveBeenCalledWith(mockEntry.path)
+ }
+ })
+
+ it('calls onSwitchTab when clicking a tab', () => {
+ const secondEntry: VaultEntry = {
+ ...mockEntry,
+ path: '/vault/topic/dev.md',
+ title: 'Dev Topic',
+ isA: 'Topic',
+ }
+ const onSwitchTab = vi.fn()
+ render(
+
+ )
+ fireEvent.click(screen.getByText('Dev Topic'))
+ expect(onSwitchTab).toHaveBeenCalledWith(secondEntry.path)
+ })
+
+ it('renders new note button in tab bar', () => {
+ const onCreateNote = vi.fn()
+ render(
+
+ )
+ const newNoteBtn = screen.getByTitle('New note')
+ expect(newNoteBtn).toBeInTheDocument()
+ fireEvent.click(newNoteBtn)
+ expect(onCreateNote).toHaveBeenCalled()
+ })
+
+ it('shows BlockNote editor when a tab is active', () => {
+ render(
+
+ )
+ expect(screen.getByTestId('blocknote-view')).toBeInTheDocument()
+ })
+
+ it('shows modified indicator when file is modified', () => {
+ render(
+ true}
+ />
+ )
+ // Modified indicator shows "M" in the breadcrumb
+ expect(screen.getByText('M')).toBeInTheDocument()
+ })
+
+ it('renders diff toggle button when file is modified', () => {
+ render(
+ true}
+ onLoadDiff={async () => '+ added line'}
+ />
+ )
+ const diffBtn = screen.getByTitle('Show diff')
+ expect(diffBtn).toBeInTheDocument()
+ })
+
+ it('includes inspector panel', () => {
+ render(
+
+ )
+ // Inspector renders "Properties" header
+ expect(screen.getAllByText('Properties').length).toBeGreaterThan(0)
+ })
+})