import { beforeEach, describe, expect, it, vi } from 'vitest'; import { TFile, TFolder, TAbstractFile, MetadataCache, FileManager } from 'obsidian'; import { VaultCacheService } from '../../Services/VaultCacheService'; import { VaultService } from '../../Services/VaultService'; import { SettingsService, type IVaultkeeperAISettings } from '../../Services/SettingsService'; import { makeTestSettings } from '../Helpers/makeTestSettings'; import { SanitiserService } from '../../Services/SanitiserService'; import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService'; import { Services } from '../../Services/Services'; import type VaultkeeperAIPlugin from '../../main'; import { ChatMode } from '../../Enums/ChatMode'; // Mock getAllTags from obsidian vi.mock('obsidian', async () => { const actual = await vi.importActual('obsidian'); return { ...actual, Component: class Component { public load() {} public onload() {} public unload() {} public onunload() {} public addChild(component: T): T { return component; } public removeChild(component: T): T { return component; } public register(_cb: () => any): void {} public registerEvent(_eventRef: any): void {} public registerDomEvent(_el: any, _type: any, _callback: any, _options?: any): void {} public registerInterval(id: number): number { return id; } }, getAllTags: vi.fn((metadata: any) => { if (!metadata || !metadata.tags) return null; return metadata.tags.map((t: any) => t.tag); }) }; }); /** * Performance Test Suite for VaultCacheService * * Tests the performance of cache operations on extremely large vaults (up to 20,000 files) * focusing on initial cache loading and fuzzy search performance. * * Test Matrix: * - Initial Cache Loading: 1K, 5K, 10K, 20K files × 3 tag densities = 12 tests * - Fuzzy Search - matchTag: 4 vault sizes × 3 query patterns = 12 tests * - Fuzzy Search - matchFile: 4 vault sizes × 3 query patterns = 12 tests * - Fuzzy Search - matchFolder: 3 folder configurations × 3 query patterns = 9 tests * - Real-time Updates: 3 update operations × 2 scales = 6 tests * * Total: 51 performance tests */ // ============================================================================ // Mock Setup // ============================================================================ const mockVault = { getMarkdownFiles: vi.fn(), getAbstractFileByPath: vi.fn(), adapter: { list: vi.fn() }, on: vi.fn() } as any; const mockFileManager = { processFrontMatter: vi.fn() } as unknown as FileManager; const mockMetadataCache = { getCache: vi.fn(), on: vi.fn() } as unknown as MetadataCache; const mockPlugin = { app: { vault: mockVault, fileManager: mockFileManager, metadataCache: mockMetadataCache }, saveData: vi.fn().mockResolvedValue(undefined), registerEvent: vi.fn() } as unknown as VaultkeeperAIPlugin; const mockSettings: IVaultkeeperAISettings = makeTestSettings({ firstTimeStart: false, apiKeys: { claude: 'test-claude-key', openai: 'test-openai-key', gemini: 'test-gemini-key', mistral: 'test-mistral-key', local: 'test-local-key' }, searchResultsLimit: 15, snippetSizeLimit: 300, chatMode: ChatMode.Edit }); let settingsService: SettingsService; let vaultService: VaultService; let vaultCacheService: VaultCacheService; let fileEventHandler: any; let resolvedHandler: any; // ============================================================================ // Test Configuration // ============================================================================ // Performance thresholds (milliseconds) // These are set based on observed performance with some headroom for slower machines const PERFORMANCE_THRESHOLDS = { initialCache: { '1000': 20, // Observed: 2-3ms, threshold: 20ms '5000': 40, // Observed: 5-6ms, threshold: 40ms '10000': 80, // Observed: 8-16ms, threshold: 80ms '20000': 150 // Observed: 18-29ms, threshold: 150ms }, fuzzySearch: { simple: 20, // Observed: < 10ms, threshold: 20ms partial: 20, // Observed: < 10ms, threshold: 20ms complex: 30 // Observed: < 10ms, threshold: 30ms }, realtimeUpdate: { single: 10, // Observed: 1-2ms, threshold: 10ms batch: 100 // Observed: 43-45ms, threshold: 100ms } }; // Tag densities for cache loading tests const TAG_DENSITIES = { none: 0, // No tags medium: 0.5, // 50% of files have tags high: 1.0 // 100% of files have tags }; // ============================================================================ // Helper Functions // ============================================================================ /** * Creates a mock TFile with specified path */ function createMockFile(path: string): TFile { const name = path.split('/').pop() || ''; const basename = name.split('.')[0]; const file = new TFile(); file.path = path; file.name = name; file.basename = basename; file.extension = 'md'; file.stat = { ctime: Date.now(), mtime: Date.now(), size: 100 }; file.parent = null; file.vault = mockVault as any; return file; } /** * Creates a mock TFolder with specified path */ function createMockFolder(path: string, children: TAbstractFile[] = []): TFolder { const name = path.split('/').pop() || ''; const folder = new TFolder(); folder.path = path; folder.name = name; folder.children = children; folder.parent = null; folder.vault = mockVault as any; return folder; } /** * Generates realistic tags for a file */ function generateTags(tagDensity: number): string[] { if (Math.random() > tagDensity) { return []; } const tagPool = [ 'project', 'meeting', 'todo', 'important', 'archive', 'personal', 'work', 'research', 'ideas', 'reference', 'daily-note', 'weekly-review', 'planning', 'documentation', 'code', 'design', 'frontend', 'backend', 'testing' ]; const numTags = Math.floor(Math.random() * 3) + 1; // 1-3 tags per file const tags: string[] = []; for (let i = 0; i < numTags; i++) { const tag = tagPool[Math.floor(Math.random() * tagPool.length)]; if (!tags.includes(tag)) { tags.push(`#${tag}`); } } return tags; } /** * Generates a vault with specified parameters */ function generateVault(config: { fileCount: number; tagDensity: number; folderDepth?: 'flat' | 'medium' | 'deep'; }): { files: TFile[]; folders: TFolder[]; allItems: TAbstractFile[]; tagMap: Map; } { const files: TFile[] = []; const folders: TFolder[] = []; const allItems: TAbstractFile[] = []; const tagMap = new Map(); // Generate folder structure based on depth configuration const folderDepth = config.folderDepth || 'medium'; const folderPaths: string[] = []; if (folderDepth === 'flat') { // Flat structure: 5-10 top-level folders const folderNames = ['Notes', 'Projects', 'Archive', 'Daily', 'Resources', 'Personal', 'Work', 'Research']; folderPaths.push(...folderNames); } else if (folderDepth === 'medium') { // Medium structure: 2-3 levels deep const topFolders = ['Notes', 'Projects', 'Archive', 'Daily', 'Resources']; topFolders.forEach(top => { folderPaths.push(top); for (let i = 0; i < 3; i++) { folderPaths.push(`${top}/Subfolder-${i}`); } }); } else { // Deep structure: 3-5 levels deep const topFolders = ['Notes', 'Projects', 'Archive']; topFolders.forEach(top => { folderPaths.push(top); for (let i = 0; i < 5; i++) { const level2 = `${top}/Category-${i}`; folderPaths.push(level2); for (let j = 0; j < 3; j++) { folderPaths.push(`${level2}/Subcategory-${j}`); } } }); } // Create folder objects folderPaths.forEach(path => { const folder = createMockFolder(path); folders.push(folder); allItems.push(folder); }); // Generate files distributed across folders for (let i = 0; i < config.fileCount; i++) { const folderPath = folderPaths[Math.floor(Math.random() * folderPaths.length)]; const path = `${folderPath}/note-${i}.md`; const file = createMockFile(path); files.push(file); allItems.push(file); // Generate tags for this file const tags = generateTags(config.tagDensity); tagMap.set(path, tags); } return { files, folders, allItems, tagMap }; } /** * Configures mocks with generated vault data */ function setupVaultMocks( files: TFile[], folders: TFolder[], allItems: TAbstractFile[], tagMap: Map ): void { (mockVault.getMarkdownFiles as ReturnType).mockReturnValue(files); (mockVault.getAbstractFileByPath as ReturnType).mockImplementation((path: string) => { if (path === '/' || path === '') return createMockFolder('/', allItems); return allItems.find(f => f.path === path) || null; }); // Mock metadata cache for tags (mockMetadataCache.getCache as ReturnType).mockImplementation((path: string) => { const tags = tagMap.get(path); if (!tags || tags.length === 0) return null; return { tags: tags.map(tag => ({ tag, position: { start: { line: 0, col: 0, offset: 0 }, end: { line: 0, col: 0, offset: 0 } } })) }; }); // Mock folder traversal (mockVault.adapter.list as ReturnType).mockResolvedValue({ files: files.map(f => f.path), folders: folders.map(f => f.path) }); } /** * Sets up VaultService with mock implementation */ function setupVaultService(allItems: TAbstractFile[]): void { // Mock listDirectoryContents to return all items vaultService.listDirectoryContents = vi.fn().mockResolvedValue(allItems); vaultService.isExclusion = vi.fn().mockReturnValue(false); // Capture file event handler vaultService.registerFileEvents = vi.fn((handler) => { fileEventHandler = handler; }); } /** * Measures initial cache loading performance */ async function measureCacheLoading(): Promise<{ duration: number; memoryUsed: number; tagCount: number; fileCount: number; folderCount: number; }> { // Force garbage collection if available if (global.gc) { global.gc(); } const memBefore = process.memoryUsage().heapUsed; const startTime = performance.now(); // Trigger cache setup by calling the resolved handler await resolvedHandler(); const duration = performance.now() - startTime; const memAfter = process.memoryUsage().heapUsed; const memoryUsed = memAfter - memBefore; // Access internal state for verification (using type assertion) const service = vaultCacheService as any; const tagCount = service.tags.size; const fileCount = service.files.size; const folderCount = service.folders.size; return { duration, memoryUsed, tagCount, fileCount, folderCount }; } /** * Measures fuzzy search performance */ function measureFuzzySearch( searchFn: (input: string) => any, query: string ): { duration: number; resultCount: number; } { const startTime = performance.now(); const results = searchFn(query); const duration = performance.now() - startTime; return { duration, resultCount: results.length }; } /** * Creates an initial cache loading test */ function createCacheLoadingTest( fileCount: number, tagDensity: number, tagDensityLabel: string ) { return async () => { // Generate vault const { files, folders, allItems, tagMap } = generateVault({ fileCount, tagDensity }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); // Create fresh service instance vaultCacheService = new VaultCacheService(); // Measure performance const metrics = await measureCacheLoading(); // Log results console.log(` 📊 ${fileCount.toLocaleString()} files, ${tagDensityLabel} tags: ${metrics.duration.toFixed(0)}ms (${metrics.tagCount} tags, ${metrics.fileCount} files, ${metrics.folderCount} folders)`); // Assertions expect(metrics.fileCount).toBe(fileCount); expect(metrics.folderCount).toBeGreaterThan(0); const thresholdKey = String(fileCount) as keyof typeof PERFORMANCE_THRESHOLDS.initialCache; if (PERFORMANCE_THRESHOLDS.initialCache[thresholdKey]) { expect(metrics.duration).toBeLessThan(PERFORMANCE_THRESHOLDS.initialCache[thresholdKey]); } // Memory should be reasonable (< 500MB) expect(metrics.memoryUsed).toBeLessThan(500 * 1024 * 1024); }; } /** * Creates a fuzzy search test */ function createFuzzySearchTest( fileCount: number, searchType: 'tag' | 'file' | 'folder', query: string, queryLabel: string, folderDepth?: 'flat' | 'medium' | 'deep' ) { return async () => { // Generate vault const config = { fileCount, tagDensity: 0.5, // Medium tag density for search tests folderDepth }; const { files, folders, allItems, tagMap } = generateVault(config); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); // Create and initialize service vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Select search function const searchFn = searchType === 'tag' ? (q: string) => vaultCacheService.matchTag(q) : searchType === 'file' ? (q: string) => vaultCacheService.matchFile(q) : (q: string) => vaultCacheService.matchFolder(q); // Measure search performance const metrics = measureFuzzySearch(searchFn, query); // Log results const contextInfo = folderDepth ? `, ${folderDepth} folders` : ''; console.log(` 📊 ${fileCount.toLocaleString()} files, ${queryLabel} query${contextInfo}: ${metrics.duration.toFixed(2)}ms (${metrics.resultCount} results)`); // Assertions expect(metrics.resultCount).toBeLessThanOrEqual(10); // fuzzysort limit expect(metrics.duration).toBeLessThan(PERFORMANCE_THRESHOLDS.fuzzySearch[queryLabel as keyof typeof PERFORMANCE_THRESHOLDS.fuzzySearch]); }; } // ============================================================================ // Test Setup // ============================================================================ beforeEach(() => { DeregisterAllServices(); // Register services RegisterSingleton(Services.VaultkeeperAIPlugin, mockPlugin); RegisterSingleton(Services.SanitiserService, new SanitiserService()); // Mock EventService and DiffService to avoid Obsidian Events dependency const mockEventService = { trigger: vi.fn(), on: vi.fn(), off: vi.fn() }; const mockDiffService = { requestDiff: vi.fn().mockResolvedValue({ accepted: true }), onAccept: vi.fn(), onReject: vi.fn(), onSuggest: vi.fn() }; RegisterSingleton(Services.EventService, mockEventService as any); RegisterSingleton(Services.DiffService, mockDiffService as any); // Create settings service settingsService = new SettingsService(mockSettings); RegisterSingleton(Services.SettingsService, settingsService); // Create vault service vaultService = new VaultService(); RegisterSingleton(Services.VaultService, vaultService); // Capture the metadata resolved handler (mockMetadataCache.on as ReturnType).mockImplementation((event: string, handler: any) => { if (event === 'resolved') { resolvedHandler = handler; } }); // Reset mocks vi.clearAllMocks(); }); // ============================================================================ // Performance Tests // ============================================================================ describe('VaultCacheService Performance Tests', () => { // Extend timeout for performance tests const PERFORMANCE_TIMEOUT = 30000; // ======================================================================== // Initial Cache Loading Performance // ======================================================================== describe('Initial Cache Loading Performance', () => { describe('1,000 files', () => { const fileCount = 1000; it('no tags (0%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.none, 'no'), PERFORMANCE_TIMEOUT ); it('medium tag density (50%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.medium, 'medium'), PERFORMANCE_TIMEOUT ); it('high tag density (100%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.high, 'high'), PERFORMANCE_TIMEOUT ); }); describe('5,000 files', () => { const fileCount = 5000; it('no tags (0%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.none, 'no'), PERFORMANCE_TIMEOUT ); it('medium tag density (50%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.medium, 'medium'), PERFORMANCE_TIMEOUT ); it('high tag density (100%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.high, 'high'), PERFORMANCE_TIMEOUT ); }); describe('10,000 files', () => { const fileCount = 10000; it('no tags (0%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.none, 'no'), PERFORMANCE_TIMEOUT ); it('medium tag density (50%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.medium, 'medium'), PERFORMANCE_TIMEOUT ); it('high tag density (100%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.high, 'high'), PERFORMANCE_TIMEOUT ); }); describe('20,000 files', () => { const fileCount = 20000; it('no tags (0%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.none, 'no'), PERFORMANCE_TIMEOUT ); it('medium tag density (50%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.medium, 'medium'), PERFORMANCE_TIMEOUT ); it('high tag density (100%)', createCacheLoadingTest(fileCount, TAG_DENSITIES.high, 'high'), PERFORMANCE_TIMEOUT ); }); }); // ======================================================================== // Fuzzy Search Performance - matchTag // ======================================================================== describe('Fuzzy Search Performance - matchTag', () => { const queries = { simple: 'project', partial: 'proj', complex: 'important-meeting' }; describe('1,000 files', () => { const fileCount = 1000; it('simple query', createFuzzySearchTest(fileCount, 'tag', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'tag', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'tag', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); describe('5,000 files', () => { const fileCount = 5000; it('simple query', createFuzzySearchTest(fileCount, 'tag', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'tag', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'tag', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); describe('10,000 files', () => { const fileCount = 10000; it('simple query', createFuzzySearchTest(fileCount, 'tag', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'tag', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'tag', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); describe('20,000 files', () => { const fileCount = 20000; it('simple query', createFuzzySearchTest(fileCount, 'tag', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'tag', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'tag', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); }); // ======================================================================== // Fuzzy Search Performance - matchFile // ======================================================================== describe('Fuzzy Search Performance - matchFile', () => { const queries = { simple: 'note', partial: 'not', complex: 'note-1234' }; describe('1,000 files', () => { const fileCount = 1000; it('simple query', createFuzzySearchTest(fileCount, 'file', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'file', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'file', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); describe('5,000 files', () => { const fileCount = 5000; it('simple query', createFuzzySearchTest(fileCount, 'file', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'file', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'file', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); describe('10,000 files', () => { const fileCount = 10000; it('simple query', createFuzzySearchTest(fileCount, 'file', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'file', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'file', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); describe('20,000 files', () => { const fileCount = 20000; it('simple query', createFuzzySearchTest(fileCount, 'file', queries.simple, 'simple'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'file', queries.partial, 'partial'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'file', queries.complex, 'complex'), PERFORMANCE_TIMEOUT ); }); }); // ======================================================================== // Fuzzy Search Performance - matchFolder // ======================================================================== describe('Fuzzy Search Performance - matchFolder', () => { const queries = { simple: 'Notes', partial: 'Not', complex: 'Projects/Category' }; describe('Flat folder structure (100 files)', () => { const fileCount = 100; // Small file count to focus on folder performance it('simple query', createFuzzySearchTest(fileCount, 'folder', queries.simple, 'simple', 'flat'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'folder', queries.partial, 'partial', 'flat'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'folder', queries.complex, 'complex', 'flat'), PERFORMANCE_TIMEOUT ); }); describe('Medium folder structure (1,000 files)', () => { const fileCount = 1000; it('simple query', createFuzzySearchTest(fileCount, 'folder', queries.simple, 'simple', 'medium'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'folder', queries.partial, 'partial', 'medium'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'folder', queries.complex, 'complex', 'medium'), PERFORMANCE_TIMEOUT ); }); describe('Deep folder structure (5,000 files)', () => { const fileCount = 5000; it('simple query', createFuzzySearchTest(fileCount, 'folder', queries.simple, 'simple', 'deep'), PERFORMANCE_TIMEOUT ); it('partial query', createFuzzySearchTest(fileCount, 'folder', queries.partial, 'partial', 'deep'), PERFORMANCE_TIMEOUT ); it('complex query', createFuzzySearchTest(fileCount, 'folder', queries.complex, 'complex', 'deep'), PERFORMANCE_TIMEOUT ); }); }); // ======================================================================== // Real-time Update Performance // ======================================================================== describe('Real-time Update Performance', () => { it('single file creation with tags', async () => { // Setup initial cache with 1000 files const { files, folders, allItems, tagMap } = generateVault({ fileCount: 1000, tagDensity: 0.5 }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Create new file const newFile = createMockFile('Notes/new-file.md'); const newTags = ['#urgent', '#todo']; tagMap.set(newFile.path, newTags); // Update mock to return new tags (mockMetadataCache.getCache as ReturnType).mockImplementation((path: string) => { const tags = tagMap.get(path); if (!tags || tags.length === 0) return null; return { tags: tags.map(tag => ({ tag, position: { start: { line: 0, col: 0, offset: 0 }, end: { line: 0, col: 0, offset: 0 } } })) }; }); // Measure file event handling performance const startTime = performance.now(); fileEventHandler('create', newFile, {}); const duration = performance.now() - startTime; console.log(` 📊 Single file creation: ${duration.toFixed(2)}ms`); expect(duration).toBeLessThan(PERFORMANCE_THRESHOLDS.realtimeUpdate.single); }, PERFORMANCE_TIMEOUT); it('batch file creation (100 files)', async () => { // Setup initial cache with 1000 files const { files, folders, allItems, tagMap } = generateVault({ fileCount: 1000, tagDensity: 0.5 }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Create 100 new files const newFiles: TFile[] = []; for (let i = 0; i < 100; i++) { const newFile = createMockFile(`Notes/batch-file-${i}.md`); const newTags = generateTags(0.5); tagMap.set(newFile.path, newTags); newFiles.push(newFile); } // Measure batch file event handling performance const startTime = performance.now(); newFiles.forEach(file => { fileEventHandler('create', file, {}); }); const duration = performance.now() - startTime; console.log(` 📊 Batch file creation (100 files): ${duration.toFixed(0)}ms`); expect(duration).toBeLessThan(PERFORMANCE_THRESHOLDS.realtimeUpdate.batch); }, PERFORMANCE_TIMEOUT); it('batch tag updates (100 files)', async () => { // Setup initial cache with 1000 files const { files, folders, allItems, tagMap } = generateVault({ fileCount: 1000, tagDensity: 0.5 }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Update tags on 100 existing files const filesToUpdate = files.slice(0, 100); // Measure batch tag update performance const startTime = performance.now(); filesToUpdate.forEach(file => { const newTags = generateTags(1.0); // Generate new tags tagMap.set(file.path, newTags); fileEventHandler('modify', file, {}); }); const duration = performance.now() - startTime; console.log(` 📊 Batch tag updates (100 files): ${duration.toFixed(0)}ms`); expect(duration).toBeLessThan(PERFORMANCE_THRESHOLDS.realtimeUpdate.batch); }, PERFORMANCE_TIMEOUT); it('batch file renames (100 files)', async () => { // Setup initial cache with 1000 files const { files, folders, allItems, tagMap } = generateVault({ fileCount: 1000, tagDensity: 0.5 }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Rename 100 files const filesToRename = files.slice(0, 100); // Measure batch rename performance const startTime = performance.now(); filesToRename.forEach((file, index) => { const oldPath = file.path; const newPath = file.path.replace(/note-\d+/, `renamed-${index}`); file.path = newPath; fileEventHandler('rename', file, { oldPath }); }); const duration = performance.now() - startTime; console.log(` 📊 Batch file renames (100 files): ${duration.toFixed(0)}ms`); expect(duration).toBeLessThan(PERFORMANCE_THRESHOLDS.realtimeUpdate.batch); }, PERFORMANCE_TIMEOUT); it('batch folder operations (50 folders)', async () => { // Setup initial cache with 1000 files const { files, folders, allItems, tagMap } = generateVault({ fileCount: 1000, tagDensity: 0.5, folderDepth: 'medium' }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Create 50 new folders const newFolders: TFolder[] = []; for (let i = 0; i < 50; i++) { const newFolder = createMockFolder(`Notes/NewFolder-${i}`); newFolders.push(newFolder); } // Measure batch folder creation performance const startTime = performance.now(); newFolders.forEach(folder => { fileEventHandler('create', folder, {}); }); const duration = performance.now() - startTime; console.log(` 📊 Batch folder creation (50 folders): ${duration.toFixed(0)}ms`); expect(duration).toBeLessThan(PERFORMANCE_THRESHOLDS.realtimeUpdate.batch); }, PERFORMANCE_TIMEOUT); it('mixed operations (files + folders + updates)', async () => { // Setup initial cache with 1000 files const { files, folders, allItems, tagMap } = generateVault({ fileCount: 1000, tagDensity: 0.5 }); setupVaultMocks(files, folders, allItems, tagMap); setupVaultService(allItems); vaultCacheService = new VaultCacheService(); await resolvedHandler(); // Perform mixed operations const startTime = performance.now(); // 20 file creates for (let i = 0; i < 20; i++) { const newFile = createMockFile(`Notes/mixed-file-${i}.md`); const newTags = generateTags(0.5); tagMap.set(newFile.path, newTags); fileEventHandler('create', newFile, {}); } // 20 tag updates files.slice(0, 20).forEach(file => { const newTags = generateTags(1.0); tagMap.set(file.path, newTags); fileEventHandler('modify', file, {}); }); // 20 renames files.slice(20, 40).forEach((file, index) => { const oldPath = file.path; const newPath = file.path.replace(/note-\d+/, `mixed-renamed-${index}`); file.path = newPath; fileEventHandler('rename', file, { oldPath }); }); // 20 folder creates for (let i = 0; i < 20; i++) { const newFolder = createMockFolder(`Projects/MixedFolder-${i}`); fileEventHandler('create', newFolder, {}); } const duration = performance.now() - startTime; console.log(` 📊 Mixed operations (80 total): ${duration.toFixed(0)}ms`); expect(duration).toBeLessThan(PERFORMANCE_THRESHOLDS.realtimeUpdate.batch); }, PERFORMANCE_TIMEOUT); }); });