// src/layout/__tests__/PluginLoaderIntegration.test.tsx import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { render, screen, cleanup, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { LayoutProvider } from '../LayoutContext' import { AppShell } from '../AppShell' import type { DocumentManager } from '../../services/document-manager' import { App, Vault, Workspace } from '../../plugin' import { createTFile } from '../../plugin/files' import { FileIndex } from '../../services/file-index' import { MarkdownView } from '../../plugin/views/markdown-view' import { createTestVaultStateData } from '../../services/__tests__/vault-state-test-helpers' import { EnabledStateStore } from '../../plugin/loader/enabled-state-store' // Default file stat for creating TFile instances const defaultStat = { ctime: 0, mtime: 0, size: 0 } // Mock the SearchContext so AppShell doesn't require a SearchProvider // Stub editor surface for the same reason as tab-characterization (#406). vi.mock('../MainAreaViewContainer', () => ({ MainAreaViewContainer: () =>
, })) vi.mock('../../services/SearchContext', () => ({ useSearch: () => ({ searchService: { search: vi.fn().mockReturnValue([]), getDocumentContent: vi.fn(), indexDocument: vi.fn(), removeDocument: vi.fn(), getDocumentHead: vi.fn(), getIndexedDocuments: vi.fn().mockReturnValue([]), serialize: vi.fn().mockReturnValue({}), deserialize: vi.fn(), clear: vi.fn(), getRecentFiles: vi.fn().mockReturnValue([]), }, metadataCache: { getBacklinks: vi.fn().mockReturnValue([]), getFileCache: vi.fn().mockReturnValue(null), getFileCacheByUrl: vi.fn().mockReturnValue(null), getAllTags: vi.fn().mockReturnValue({}), subscribe: vi.fn().mockReturnValue(() => {}), getVersion: vi.fn().mockReturnValue(0), updateFile: vi.fn(), removeFile: vi.fn(), getDisplayTitle: vi.fn().mockReturnValue(null), getIndexedDocuments: vi.fn().mockReturnValue([]), getDocumentTags: vi.fn().mockReturnValue([]), getOutgoingLinks: vi.fn().mockReturnValue([]), getResolvedOutgoingLinks: vi.fn().mockReturnValue([]), isDisplayTitleEnabled: vi.fn().mockReturnValue(true), setDisplayTitleEnabled: vi.fn(), getFirstLinkpathDest: vi.fn().mockReturnValue(null), on: vi.fn().mockReturnValue({ e: {}, n: 'changed', fn: () => {} }), offref: vi.fn(), }, indexer: { start: vi.fn(), stop: vi.fn() }, isInitialized: true, }), })) // Mock DocumentManager for tests const mockDocumentManager = { listFolder: async () => [], createFile: () => 'automerge:mock', createFolder: () => 'automerge:mock', deleteFile: async () => {}, readText: async (url: any) => ({ status: 'available' as const, documentId: url, heads: ['h0'], content: '' }), updateContent: async () => {}, } as unknown as DocumentManager // Create a mock App instance for tests function createMockApp(): App { const mockDM = { createFile: vi.fn(), createFolder: vi.fn(), readText: vi.fn(async (url: string) => ({ status: 'unavailable' as const, documentId: url })), updateContent: vi.fn(), listFolder: vi.fn().mockResolvedValue([]), } as any const vault = new Vault('TestVault', new FileIndex(mockDM, 'automerge:root' as any), mockDM) const workspace = new Workspace(document.createElement('div')) const app = new App(vault, workspace) app.reamsStateData = createTestVaultStateData() app.setDocumentManager(mockDM) // Wire metadataCache and searchService onto app — SearchProvider does // this in production but this test mocks SearchContext, so we set them // directly (#306). app.setMetadataCache({ getBacklinks: vi.fn().mockReturnValue([]), getFileCache: vi.fn().mockReturnValue(null), getFileCacheByUrl: vi.fn().mockReturnValue(null), getAllTags: vi.fn().mockReturnValue({}), subscribe: vi.fn().mockReturnValue(() => {}), getVersion: vi.fn().mockReturnValue(0), updateFile: vi.fn(), removeFile: vi.fn(), getDisplayTitle: vi.fn().mockReturnValue(null), getIndexedDocuments: vi.fn().mockReturnValue([]), getDocumentTags: vi.fn().mockReturnValue([]), getOutgoingLinks: vi.fn().mockReturnValue([]), getResolvedOutgoingLinks: vi.fn().mockReturnValue([]), isDisplayTitleEnabled: vi.fn().mockReturnValue(true), setDisplayTitleEnabled: vi.fn(), getFirstLinkpathDest: vi.fn().mockReturnValue(null), on: vi.fn().mockReturnValue({ e: {}, n: 'changed', fn: () => {} }), offref: vi.fn(), } as any) app.setSearchService({ search: vi.fn().mockReturnValue([]), getDocumentContent: vi.fn(), indexDocument: vi.fn(), removeDocument: vi.fn(), getDocumentHead: vi.fn(), getIndexedDocuments: vi.fn().mockReturnValue([]), serialize: vi.fn().mockReturnValue({}), deserialize: vi.fn(), clear: vi.fn(), getRecentFiles: vi.fn().mockReturnValue([]), } as any) return app } // Module-level app instance for tests (cleaned up in afterEach) let mockApp: App let enabledStateStore: EnabledStateStore // Built-in plugin names for UI verification const BUILTIN_PLUGIN_NAMES = [ 'File Tree', 'Quick Search', 'Wikilinks', 'Daily Notes', 'Create File', ] // Helper to create a tab via workspace (matches AppShell.openDocument) function createWorkspaceTab( app: App, docUrl: string, title: string, ): void { const workspace = app.workspace const vault = app.vault // Register file in vault's file index for lookup const root = vault.getRoot() const file = createTFile(title, root, defaultStat) ;(vault as any).fileIndex.registerFile(docUrl, 'automerge:root', file) const leaf = workspace.getLeaf(true) leaf.file = file const view = new MarkdownView(leaf) leaf.view = view view.file = file workspace.setActiveLeaf(leaf) } // Helper to render AppShell with provider function renderAppShell() { // Create a tab via workspace before rendering (replaces useEffect openTab) createWorkspaceTab(mockApp, 'automerge:1', 'Test.md') return render(