UNPKG

meld

Version:

Meld: A template language for LLM prompts

341 lines (281 loc) 11.4 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { main } from './index.js'; import { TestContext } from '@tests/utils/index.js'; import type { ProcessOptions } from '@core/types/index.js'; import { IFileSystem } from '@services/fs/FileSystemService/IFileSystem.js'; import { MeldFileNotFoundError } from '@core/errors/MeldFileNotFoundError.js'; import { DirectiveService } from '@services/pipeline/DirectiveService/DirectiveService.js'; import fs from 'fs'; import { TestDebuggerService } from '@tests/utils/debug/TestDebuggerService.js'; // Define the type for main function options type MainOptions = { fs?: IFileSystem; format?: 'xml'; services?: any; }; describe('SDK Integration Tests', () => { let context: TestContext; let testFilePath: string; beforeEach(async () => { context = new TestContext(); await context.initialize(); testFilePath = 'test.meld'; }); afterEach(async () => { await context.cleanup(); vi.resetModules(); vi.clearAllMocks(); }); describe('Service Management', () => { it('should create services in correct initialization order', async () => { // Create a new DirectiveService instance to spy on const directive = new DirectiveService(); const initSpy = vi.spyOn(directive, 'initialize'); // Create services object with our spied service const services = { ...context.services, directive }; await context.fs.writeFile(testFilePath, '@text greeting = "Hello"'); await main(testFilePath, { fs: context.fs, services: services as any }); // Verify directive.initialize was called with services in correct order expect(initSpy).toHaveBeenCalledWith( expect.any(Object), // validation expect.any(Object), // state expect.any(Object), // path expect.any(Object), // filesystem expect.any(Object), // parser expect.any(Object), // interpreter expect.any(Object), // circularity expect.any(Object) // resolution ); }); it('should allow service injection through options', async () => { const customState = context.services.state; const spy = vi.spyOn(customState, 'enableTransformation'); await context.fs.writeFile(testFilePath, '@text greeting = "Hello"'); await main(testFilePath, { fs: context.fs, services: { state: customState } as any, transformation: true }); expect(spy).toHaveBeenCalledWith(true); }); }); describe('Transformation Mode', () => { it('should enable transformation through options', async () => { const content = `@text greeting = "Hello" @run[echo test]`; await context.fs.writeFile(testFilePath, content); // Start a debug session to capture metrics const sessionId = await context.startDebugSession(); const result = await main(testFilePath, { fs: context.fs, services: context.services as any, transformation: true }); // Get debug results const debugResult = await context.endDebugSession(sessionId); // Verify debugging data expect(debugResult).toBeDefined(); expect(debugResult.metrics).toBeDefined(); expect(debugResult.startTime).toBeLessThan(debugResult.endTime); // In transformation mode, directives should be replaced expect(result).not.toContain('[run directive output placeholder]'); expect(result).toContain('test'); }); it('should respect existing transformation state', async () => { const content = '@run [echo test]'; await context.fs.writeFile(testFilePath, content); // Enable transformation through context context.enableTransformation(); const result = await main(testFilePath, { fs: context.fs, services: context.services as any }); // Should still be in transformation mode expect(result).not.toContain('[run directive output placeholder]'); expect(result).toContain('test'); }); it('should handle execution directives correctly', async () => { await context.fs.writeFile(testFilePath, '@run [echo test]'); context.enableDebug(); context.disableTransformation(); // Explicitly disable transformation const result = await main(testFilePath, { fs: context.fs, format: 'xml', services: context.services as any, // Cast to any to avoid type errors debug: true }); // Verify result expect(result).toContain('[run directive output placeholder]'); }); it('should handle complex meld content with mixed directives', async () => { const content = ` @text greeting = "Hello" @data config = { "value": 123 } Some text content @run [echo test] More text`; await context.fs.writeFile(testFilePath, content); context.disableTransformation(); // Explicitly disable transformation const result = await main(testFilePath, { fs: context.fs, services: context.services as any }); // Definition directives should be omitted expect(result).not.toContain('"identifier": "greeting"'); expect(result).not.toContain('"value": "Hello"'); expect(result).not.toContain('"identifier": "config"'); // Text content should be preserved expect(result).toContain('Some text content'); expect(result).toContain('More text'); // Execution directives should show placeholder expect(result).toContain('[run directive output placeholder]'); }); }); describe('Debug Mode', () => { it('should enable debug mode through options', async () => { await context.fs.writeFile(testFilePath, '@text greeting = "Hello"'); await main(testFilePath, { fs: context.fs, services: context.services as any, debug: true }); // Verify debug data was captured const debugData = await (context.services.debug as any).getDebugData(); expect(debugData).toBeDefined(); expect(debugData.operations).toHaveLength(1); }); }); describe('Format Conversion', () => { it('should handle definition directives correctly', async () => { await context.fs.writeFile(testFilePath, '@text greeting = "Hello"'); const result = await main(testFilePath, { fs: context.fs, services: context.services as any }); // Definition directives should be omitted from output expect(result).toBe(''); }); it('should handle execution directives correctly', async () => { await context.fs.writeFile(testFilePath, '@run [echo test]'); context.enableDebug(); context.disableTransformation(); // Explicitly disable transformation const result = await main(testFilePath, { fs: context.fs, format: 'xml', services: context.services as any, // Cast to any to avoid type errors debug: true }); // Verify result expect(result).toContain('[run directive output placeholder]'); }); it('should handle complex meld content with mixed directives', async () => { const content = ` @text greeting = "Hello" @data config = { "value": 123 } Some text content @run [echo test] More text`; await context.fs.writeFile(testFilePath, content); context.disableTransformation(); // Explicitly disable transformation const result = await main(testFilePath, { fs: context.fs, services: context.services as any }); // Definition directives should be omitted expect(result).not.toContain('"identifier": "greeting"'); expect(result).not.toContain('"value": "Hello"'); expect(result).not.toContain('"identifier": "config"'); // Text content should be preserved expect(result).toContain('Some text content'); expect(result).toContain('More text'); // Execution directives should show placeholder expect(result).toContain('[run directive output placeholder]'); }); }); describe('Error Handling', () => { it('should handle parse errors gracefully', async () => { const invalidContent = '@invalid directive'; await context.fs.writeFile(testFilePath, invalidContent); await expect(main(testFilePath, { fs: context.fs, services: context.services as any })).rejects.toThrow(); }); it('should handle missing files correctly', async () => { const nonExistentFile = 'non-existent.meld'; await expect(main(nonExistentFile, { fs: context.fs, services: context.services as any })).rejects.toThrow(MeldFileNotFoundError); }); it('should handle service initialization errors', async () => { // Create a service that will throw during initialization const brokenServices = { ...context.services, directive: undefined }; await context.fs.writeFile(testFilePath, '@text greeting = "Hello"'); await expect(main(testFilePath, { fs: context.fs, services: brokenServices as any })).rejects.toThrow(); }); }); describe('Full Pipeline Integration', () => { it('should handle the complete parse -> interpret -> convert pipeline', async () => { const content = ` @text greeting = "Hello" @run [echo {{greeting}}]`; await context.fs.writeFile(testFilePath, content); const result = await main(testFilePath, { fs: context.fs, services: context.services as any, transformation: true }); // In transformation mode, directives should be replaced with their results expect(result).toContain('Hello'); expect(result).not.toContain('@text'); expect(result).not.toContain('@run'); }); it('should preserve state and content in transformation mode', async () => { const content = ` @text greeting = "Hello" @text name = "World" @run [echo {{greeting}}, {{name}}!]`; await context.fs.writeFile(testFilePath, content); const result = await main(testFilePath, { fs: context.fs, services: context.services as any, transformation: true }); // Resolved variables should be outputted expect(result).toContain('Hello, World!'); }); }); describe('Edge Cases', () => { it.todo('should handle large files efficiently'); it.todo('should handle deeply nested imports'); }); describe('Examples', () => { it('should run api-demo-simple.meld example file', async () => { // Create a simplified test file const content = ` # Simple Example ## Title @run [echo "This is a simple example"]`; await context.fs.writeFile(testFilePath, content); const result = await main(testFilePath, { fs: context.fs, services: context.services as any, transformation: true }); // Verify the output contains the transformed content - now in XML format expect(result).toContain('<SimpleExample>'); expect(result).toContain('<Title>'); expect(result).toContain('This is a simple example'); }); }); });