Skip to main content
Glama

Visa Design System MCP Server

by MarySuneela
data-manager.test.ts13.2 kB
/** * Unit tests for DataManager class */ import { jest } from '@jest/globals'; import { readFile, access, mkdir, writeFile, rm } from 'fs/promises'; import { join } from 'path'; import { DataManager } from '../src/utils/data-manager.js'; import type { DesignToken, Component, Guideline } from '../src/types/index.js'; // Mock fs/promises jest.mock('fs/promises'); const mockReadFile = readFile as jest.MockedFunction<typeof readFile>; const mockAccess = access as jest.MockedFunction<typeof access>; // Mock chokidar jest.mock('chokidar'); import { watch } from 'chokidar'; const mockWatch = watch as jest.MockedFunction<typeof watch>; const mockWatcher = { on: jest.fn(), close: jest.fn().mockResolvedValue(undefined) }; mockWatch.mockReturnValue(mockWatcher as any); describe('DataManager', () => { let dataManager: DataManager; const testDataPath = '/test/data'; // Sample test data const sampleDesignTokens: DesignToken[] = [ { name: 'primary-blue', value: '#0066CC', category: 'color', description: 'Primary brand blue color' } ]; const sampleComponents: Component[] = [ { name: 'Button', description: 'Primary button component', category: 'form', props: [ { name: 'variant', type: 'string', required: false, description: 'Button variant' } ], variants: [ { name: 'primary', props: { variant: 'primary' }, description: 'Primary button variant' } ], examples: [ { title: 'Basic Button', description: 'A basic button example', code: '<Button>Click me</Button>', language: 'jsx' } ], guidelines: ['Use for primary actions'], accessibility: { ariaLabels: ['button'], keyboardNavigation: 'Tab to focus, Enter/Space to activate' } } ]; const sampleGuidelines: Guideline[] = [ { id: 'color-usage', title: 'Color Usage Guidelines', category: 'design', content: 'Use colors consistently across the application', tags: ['color', 'design'], lastUpdated: new Date('2023-01-01T00:00:00Z') } ]; beforeEach(() => { jest.clearAllMocks(); dataManager = new DataManager({ dataPath: testDataPath, enableFileWatching: false // Disable for most tests }); }); afterEach(async () => { await dataManager.destroy(); }); describe('constructor', () => { it('should initialize with default config values', () => { const dm = new DataManager({ dataPath: '/test' }); expect(dm).toBeInstanceOf(DataManager); }); it('should accept custom config values', () => { const dm = new DataManager({ dataPath: '/custom', enableFileWatching: false, cacheTimeout: 60000 }); expect(dm).toBeInstanceOf(DataManager); }); }); describe('loadData', () => { it('should successfully load all data types', async () => { // Mock file access and content mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); const result = await dataManager.loadData(); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data?.designTokens).toEqual(sampleDesignTokens); expect(result.data?.components).toEqual(sampleComponents); expect(result.data?.guidelines).toEqual(sampleGuidelines); }); it('should handle missing files gracefully', async () => { // Mock file access to throw (file not found) mockAccess.mockRejectedValue(new Error('File not found')); const result = await dataManager.loadData(); expect(result.success).toBe(false); expect(result.errors).toBeDefined(); expect(result.errors?.some(err => err.includes('not found'))).toBe(true); }); it('should handle invalid JSON gracefully', async () => { mockAccess.mockResolvedValue(undefined); mockReadFile.mockResolvedValue('invalid json'); const result = await dataManager.loadData(); expect(result.success).toBe(false); expect(result.errors).toBeDefined(); }); it('should validate data and report validation errors', async () => { // Create invalid data that will definitely fail validation const invalidTokens = [{ // Missing required fields description: 'Invalid token' }]; mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(invalidTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); const result = await dataManager.loadData(); // Should succeed overall because components and guidelines are valid expect(result.success).toBe(true); // But should have validation errors for the invalid tokens expect(result.errors?.some(err => err.includes('validation'))).toBe(true); // And the invalid tokens should not be in the cache expect(result.data?.designTokens).toEqual([]); }); it('should prevent concurrent loading', async () => { mockAccess.mockResolvedValue(undefined); mockReadFile.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve('{}'), 100))); const promise1 = dataManager.loadData(); const promise2 = dataManager.loadData(); const [result1, result2] = await Promise.all([promise1, promise2]); expect(result2.success).toBe(false); expect(result2.errors?.[0]).toContain('already in progress'); }); }); describe('getCachedData', () => { it('should return null when no data is cached', () => { const cachedData = dataManager.getCachedData(); expect(cachedData).toBeNull(); }); it('should return cached data after successful load', async () => { mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await dataManager.loadData(); const cachedData = dataManager.getCachedData(); expect(cachedData).toBeDefined(); expect(cachedData?.designTokens).toEqual(sampleDesignTokens); }); }); describe('isCacheValid', () => { it('should return false when no cache exists', () => { expect(dataManager.isCacheValid()).toBe(false); }); it('should return true for fresh cache', async () => { mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await dataManager.loadData(); expect(dataManager.isCacheValid()).toBe(true); }); it('should return false for expired cache', async () => { const shortTimeoutManager = new DataManager({ dataPath: testDataPath, cacheTimeout: 1 // 1ms timeout }); mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await shortTimeoutManager.loadData(); // Wait for cache to expire await new Promise(resolve => setTimeout(resolve, 10)); expect(shortTimeoutManager.isCacheValid()).toBe(false); await shortTimeoutManager.destroy(); }); }); describe('validateCachedData', () => { it('should return invalid when no cache exists', () => { const result = dataManager.validateCachedData(); expect(result.valid).toBe(false); expect(result.errors?.[0]).toContain('No cached data'); }); it('should return valid for good cached data', async () => { mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await dataManager.loadData(); const result = dataManager.validateCachedData(); expect(result.valid).toBe(true); expect(result.errors).toBeUndefined(); }); }); describe('file watching', () => { it('should start file watching when enabled', async () => { const watchingManager = new DataManager({ dataPath: testDataPath, enableFileWatching: true }); mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await watchingManager.initialize(); expect(mockWatch).toHaveBeenCalled(); expect(mockWatcher.on).toHaveBeenCalledWith('change', expect.any(Function)); expect(mockWatcher.on).toHaveBeenCalledWith('add', expect.any(Function)); expect(mockWatcher.on).toHaveBeenCalledWith('unlink', expect.any(Function)); await watchingManager.destroy(); }); it('should emit events when files change', async () => { const watchingManager = new DataManager({ dataPath: testDataPath, enableFileWatching: true }); mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await watchingManager.initialize(); const fileChangedSpy = jest.fn(); watchingManager.on('fileChanged', fileChangedSpy); // Simulate file change const changeHandler = mockWatcher.on.mock.calls.find(call => call[0] === 'change')?.[1]; if (changeHandler) { changeHandler('/test/file.json'); } expect(fileChangedSpy).toHaveBeenCalledWith('/test/file.json'); await watchingManager.destroy(); }); }); describe('initialize', () => { it('should load data and start watching', async () => { const watchingManager = new DataManager({ dataPath: testDataPath, enableFileWatching: true }); mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); const result = await watchingManager.initialize(); expect(result.success).toBe(true); expect(mockWatch).toHaveBeenCalled(); await watchingManager.destroy(); }); it('should not start watching if data loading fails', async () => { const watchingManager = new DataManager({ dataPath: testDataPath, enableFileWatching: true }); mockAccess.mockRejectedValue(new Error('File not found')); const result = await watchingManager.initialize(); expect(result.success).toBe(false); expect(mockWatch).not.toHaveBeenCalled(); await watchingManager.destroy(); }); }); describe('destroy', () => { it('should cleanup watcher and cache', async () => { const watchingManager = new DataManager({ dataPath: testDataPath, enableFileWatching: true }); mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); await watchingManager.initialize(); expect(watchingManager.getCachedData()).toBeDefined(); await watchingManager.destroy(); expect(mockWatcher.close).toHaveBeenCalled(); expect(watchingManager.getCachedData()).toBeNull(); }); }); describe('event emission', () => { it('should emit dataLoaded event after successful load', async () => { mockAccess.mockResolvedValue(undefined); mockReadFile .mockResolvedValueOnce(JSON.stringify(sampleDesignTokens)) .mockResolvedValueOnce(JSON.stringify(sampleComponents)) .mockResolvedValueOnce(JSON.stringify(sampleGuidelines)); const dataLoadedSpy = jest.fn(); dataManager.on('dataLoaded', dataLoadedSpy); await dataManager.loadData(); expect(dataLoadedSpy).toHaveBeenCalledWith(expect.objectContaining({ designTokens: sampleDesignTokens, components: sampleComponents, guidelines: sampleGuidelines })); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MarySuneela/mcp-vpds'

If you have feedback or need assistance with the MCP directory API, please join our Discord server