From 0c2cfe13bf4bb60bf275b9702de04eda78a91af2 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Tue, 17 Feb 2026 11:59:17 +0100 Subject: [PATCH] test: add App.tsx integration tests - Renders four-panel layout with sidebar, note list, editor - Loads and displays vault entries from mock data - Shows empty state when no note is selected - Keyboard shortcut Cmd+S shows toast - Verifies default sidebar selection (All Notes) --- src/App.test.tsx | 160 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/App.test.tsx diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 00000000..fdb1f84d --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,160 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +// Mock @tauri-apps/api/core before importing App +vi.mock('@tauri-apps/api/core', () => ({ + invoke: vi.fn(), +})) + +// Mock mock-tauri module +const mockEntries = [ + { + 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, + }, + { + path: '/vault/topic/dev.md', + filename: 'dev.md', + title: 'Software Development', + isA: 'Topic', + aliases: ['Dev'], + belongsTo: [], + relatedTo: [], + status: null, + owner: null, + cadence: null, + modifiedAt: 1700000000, + createdAt: null, + fileSize: 256, + }, +] + +const mockAllContent: Record = { + '/vault/project/test.md': '---\ntitle: Test Project\nis_a: Project\n---\n\n# Test Project\n\nSome content.', + '/vault/topic/dev.md': '---\ntitle: Software Development\nis_a: Topic\n---\n\n# Software Development\n', +} + +vi.mock('./mock-tauri', () => ({ + isTauri: () => false, + mockInvoke: vi.fn(async (cmd: string) => { + if (cmd === 'list_vault') return mockEntries + if (cmd === 'get_all_content') return mockAllContent + if (cmd === 'get_modified_files') return [] + if (cmd === 'get_note_content') return mockAllContent['/vault/project/test.md'] || '' + if (cmd === 'get_file_history') return [] + return null + }), + addMockEntry: vi.fn(), + updateMockContent: vi.fn(), +})) + +// Mock BlockNote components (they need DOM APIs not available in jsdom) +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 App from './App' + +describe('App', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('renders the four-panel layout', async () => { + render() + // Wait for vault to load + await waitFor(() => { + expect(screen.getByText('All Notes')).toBeInTheDocument() + }) + }) + + it('loads and displays vault entries in sidebar', async () => { + render() + await waitFor(() => { + // Entries appear in both Sidebar and NoteList + expect(screen.getAllByText('Test Project').length).toBeGreaterThan(0) + expect(screen.getAllByText('Software Development').length).toBeGreaterThan(0) + }) + }) + + it('shows empty state in editor when no note is selected', async () => { + render() + await waitFor(() => { + expect(screen.getByText('Select a note to start editing')).toBeInTheDocument() + }) + }) + + it('shows keyboard shortcut hints', async () => { + render() + await waitFor(() => { + expect(screen.getByText(/Cmd\+P to search/)).toBeInTheDocument() + }) + }) + + it('registers keyboard shortcuts without error', async () => { + render() + await waitFor(() => { + expect(screen.getByText('All Notes')).toBeInTheDocument() + }) + + // Cmd+S shows toast + fireEvent.keyDown(window, { key: 's', metaKey: true }) + await waitFor(() => { + expect(screen.getByText('Saved')).toBeInTheDocument() + }) + }) + + it('renders sidebar with correct default selection (All Notes)', async () => { + render() + await waitFor(() => { + // "All Notes" should be rendered as the selected nav item + expect(screen.getByText('All Notes')).toBeInTheDocument() + expect(screen.getByText('Favorites')).toBeInTheDocument() + }) + }) + + it('renders status bar', async () => { + render() + // StatusBar should be present + await waitFor(() => { + expect(screen.getByText('All Notes')).toBeInTheDocument() + }) + // The status bar element should exist in the DOM + const appShell = document.querySelector('.app-shell') + expect(appShell).toBeInTheDocument() + }) +})