From 3572d0238bbd5151e5be9a85c7d5cae656ace4c7 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Tue, 17 Feb 2026 11:59:21 +0100 Subject: [PATCH] test: add Editor.tsx unit tests - Empty state rendering with keyboard shortcut hints - Tab bar rendering and switching between tabs - Close tab interaction - Breadcrumb bar with note type, title, and word count - New note button in tab bar - BlockNote editor rendering - Modified indicator and diff toggle button - Inspector panel integration --- src/components/Editor.test.tsx | 220 +++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/components/Editor.test.tsx 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) + }) +})