Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185// src/plugin/__tests__/vault-sidebar-integration.test.tsimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'import type { AutomergeUrl } from '@automerge/automerge-repo'import { Vault } from '../vault'import { FileIndex } from '../../services/file-index'
/** * Create a mock DocumentManager that simulates the real folder/file hierarchy. * Files are stored in folders' docs[] arrays, just like real Automerge folders. */function createMockDocumentManager() { const files = new Map<AutomergeUrl, { name: string; content: string }>() const folders = new Map<AutomergeUrl, { docs: Array<{ name: string; type: string; url: AutomergeUrl }> }>() let counter = 0 return { folders, createFile: vi.fn((name: string) => { const url = `automerge:file-${++counter}` as AutomergeUrl files.set(url, { name, content: '' }) return url }), createFolder: vi.fn(() => { const url = `automerge:folder-${++counter}` as AutomergeUrl folders.set(url, { docs: [] }) return url }), createFileInFolder: vi.fn(async (folderUrl: AutomergeUrl, name: string, _options?: unknown, initialContent = '', beforeLink?: (url: AutomergeUrl) => void) => { const fileUrl = `automerge:file-${++counter}` as AutomergeUrl files.set(fileUrl, { name, content: initialContent }) beforeLink?.(fileUrl) const folder = folders.get(folderUrl) || { docs: [] } folder.docs.push({ name, type: 'file', url: fileUrl }) folders.set(folderUrl, folder) return fileUrl }), createSubfolder: vi.fn(async (parentUrl: AutomergeUrl, name: string) => { const folderUrl = `automerge:folder-${++counter}` as AutomergeUrl folders.set(folderUrl, { docs: [] }) const parent = folders.get(parentUrl) || { docs: [] } parent.docs.push({ name, type: 'folder', url: folderUrl }) folders.set(parentUrl, parent) return folderUrl }), updateContent: vi.fn(async (url: AutomergeUrl, content: string) => { const file = files.get(url) if (file) file.content = content }), readText: vi.fn(async (url: AutomergeUrl) => { const file = files.get(url) return file ? { status: 'available' as const, documentId: url, heads: ['h0'], content: file.content } : { status: 'not-found' as const, documentId: url } }), listFolder: vi.fn(async (url: AutomergeUrl) => { return folders.get(url)?.docs ?? [] }), getFolderHandle: vi.fn(async (url: AutomergeUrl) => { const doc = { '@patchwork': { type: 'folder' }, docs: folders.get(url)?.docs ?? [] } return { doc: () => doc, on: () => {}, off: () => {} } }), deleteFile: vi.fn(), deleteFolder: vi.fn(), }}
describe('Vault + Sidebar Integration', () => { let mockDm: ReturnType<typeof createMockDocumentManager> let rootFolderUrl: AutomergeUrl beforeEach(() => { localStorage.clear() mockDm = createMockDocumentManager() // Create root folder rootFolderUrl = mockDm.createFolder() }) afterEach(() => { localStorage.clear() }) it('file created via vault.create appears in folder docs array', async () => { const vault = new Vault('Test', new FileIndex(mockDm as any, rootFolderUrl), mockDm as any) await vault.create('2026-03-16.md', '# Daily Note') // Verify file was added to root folder's docs array via createFileInFolder expect(mockDm.createFileInFolder).toHaveBeenCalledWith(rootFolderUrl, '2026-03-16.md', undefined, '# Daily Note', expect.any(Function)) // List folder should now include the file (this is what FileTree calls) const contents = await mockDm.listFolder(rootFolderUrl) expect(contents).toHaveLength(1) expect(contents[0].name).toBe('2026-03-16.md') expect(contents[0].type).toBe('file') }) it('nested file auto-creates parent folder', async () => { const vault = new Vault('Test', new FileIndex(mockDm as any, rootFolderUrl), mockDm as any) // Create file in nested path - folder should be auto-created await vault.create('daily/2026-03-16.md', '# Daily Note') // Verify folder was created as subfolder of root expect(mockDm.createSubfolder).toHaveBeenCalledWith(rootFolderUrl, 'daily', undefined, expect.any(Function)) // Verify file was created in the daily folder (not root) expect(mockDm.createFileInFolder).toHaveBeenCalled() const createFileCall = (mockDm.createFileInFolder as ReturnType<typeof vi.fn>).mock.calls[0] expect(createFileCall[1]).toBe('2026-03-16.md') // First arg should be the newly created daily folder URL expect(createFileCall[0]).not.toBe(rootFolderUrl) }) it('vault emits create event for file tree refresh', async () => { const vault = new Vault('Test', new FileIndex(mockDm as any, rootFolderUrl), mockDm as any) const createCallback = vi.fn() vault.on('create', createCallback) await vault.create('test.md', 'content') expect(createCallback).toHaveBeenCalled() const file = createCallback.mock.calls[0][0] expect(file.name).toBe('test.md') expect(file.parent).not.toBeNull() }) it('vault emits create event for auto-created folders', async () => { const vault = new Vault('Test', new FileIndex(mockDm as any, rootFolderUrl), mockDm as any) const createCallback = vi.fn() vault.on('create', createCallback) await vault.create('a/b/c.md', 'content') // Should emit create for folder 'a', folder 'b', and file 'c.md' expect(createCallback).toHaveBeenCalledTimes(3) const created = createCallback.mock.calls.map(c => c[0]) expect(created.some(f => f.name === 'a')).toBe(true) expect(created.some(f => f.name === 'b')).toBe(true) expect(created.some(f => f.name === 'c.md')).toBe(true) }) it('detects existing folder and does not create duplicate', async () => { // Simulate a folder that exists from a "previous session" (not in Vault's path registry) const existingDailyUrl = 'automerge:existing-daily' as AutomergeUrl // The adopted folder has a real parent link and an initially empty folder body. ;(mockDm as any).folders.set(existingDailyUrl, { docs: [] }) mockDm.listFolder.mockImplementation(async (url: AutomergeUrl) => { if (url === rootFolderUrl) { return [{ name: 'daily', type: 'folder', url: existingDailyUrl }] } return (mockDm as any).folders.get(url)?.docs ?? [] }) mockDm.getFolderHandle.mockImplementation(async (url: AutomergeUrl) => { const doc = { '@patchwork': { type: 'folder' }, docs: await mockDm.listFolder(url) } return { doc: () => doc, on: () => {}, off: () => {} } }) const vault = new Vault('Test', new FileIndex(mockDm as any, rootFolderUrl), mockDm as any) await vault.create('daily/note.md', 'content') // Should NOT create daily folder (detected it exists via listFolder) expect(mockDm.createSubfolder).not.toHaveBeenCalled() // Should create file in the existing daily folder expect(mockDm.createFileInFolder).toHaveBeenCalledWith(existingDailyUrl, 'note.md', undefined, 'content', expect.any(Function)) }) it('reuses folder from path registry for subsequent creates', async () => { const vault = new Vault('Test', new FileIndex(mockDm as any, rootFolderUrl), mockDm as any) // First file creates the daily folder await vault.create('daily/first.md', 'content') expect(mockDm.createSubfolder).toHaveBeenCalledTimes(1) // Second file reuses the folder (no new createSubfolder call) await vault.create('daily/second.md', 'more content') expect(mockDm.createSubfolder).toHaveBeenCalledTimes(1) // Still just 1 expect(mockDm.createFileInFolder).toHaveBeenCalledTimes(2) })})