UNPKG

meld

Version:

Meld: A template language for LLM prompts

324 lines (287 loc) 12.4 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import type { DirectiveNode, DirectiveContext } from 'meld-spec'; import { ImportDirectiveHandler } from './ImportDirectiveHandler.js'; import type { IValidationService } from '@services/resolution/ValidationService/IValidationService.js'; import type { IStateService } from '@services/state/StateService/IStateService.js'; import type { IResolutionService } from '@services/resolution/ResolutionService/IResolutionService.js'; import type { IFileSystemService } from '@services/fs/FileSystemService/IFileSystemService.js'; import type { IParserService } from '@services/pipeline/ParserService/IParserService.js'; import type { IInterpreterService } from '@services/pipeline/InterpreterService/IInterpreterService.js'; import type { ICircularityService } from '@services/resolution/CircularityService/ICircularityService.js'; import { createLocation } from '@tests/utils/testFactories.js'; // Import centralized syntax examples and helpers import { importDirectiveExamples } from '@core/syntax/index.js'; import { createNodeFromExample } from '@core/syntax/helpers'; /** * MIGRATION NOTES: * * This file has been migrated to use centralized syntax examples where possible. * * Some key observations from the migration: * * 1. For the first test "should return empty text node when transformation enabled", * we were able to use the centralized 'basicImport' example directly. * * 2. For the tests involving specific importList formats (second and third tests), * we kept the direct node creation approach since: * - The specific format of importList is important for the test behavior * - The handler expects a particular structure for these specialized cases * * 3. For the error handling test, we were able to use a modified version of the * centralized example by replacing the file path with a non-existent one. * * These decisions were made to balance using centralized examples while ensuring * the tests continue to properly test the handler's specific behaviors, particularly * around the importList parameter which is crucial for the transformation tests. */ /** * Creates a DirectiveNode from example code string * * @param code - The directive code to parse * @returns The parsed DirectiveNode */ async function createNodeFromExample(code: string): Promise<DirectiveNode> { try { const { parse } = await import('meld-ast'); const result = await parse(code, { trackLocations: true, validateNodes: true, // @ts-expect-error - structuredPaths is used but may be missing from typings structuredPaths: true }); const nodes = result.ast || []; if (!nodes || nodes.length === 0) { throw new Error(`Failed to parse example: ${code}`); } // The first node should be our directive const directiveNode = nodes[0]; if (directiveNode.type !== 'Directive') { throw new Error(`Example did not produce a directive node: ${code}`); } return directiveNode as DirectiveNode; } catch (error) { console.error('Error parsing with meld-ast:', error); throw error; } } describe('ImportDirectiveHandler Transformation', () => { let handler: ImportDirectiveHandler; let validationService: IValidationService; let stateService: IStateService; let resolutionService: IResolutionService; let fileSystemService: IFileSystemService; let parserService: IParserService; let interpreterService: IInterpreterService; let circularityService: ICircularityService; let clonedState: IStateService; let childState: IStateService; beforeEach(() => { validationService = { validate: vi.fn() } as unknown as IValidationService; childState = { setTextVar: vi.fn(), setDataVar: vi.fn(), setPathVar: vi.fn(), setCommand: vi.fn(), getTextVar: vi.fn(), getDataVar: vi.fn(), getPathVar: vi.fn(), getCommand: vi.fn(), getAllTextVars: vi.fn().mockReturnValue(new Map()), getAllDataVars: vi.fn().mockReturnValue(new Map()), getAllPathVars: vi.fn().mockReturnValue(new Map()), getAllCommands: vi.fn().mockReturnValue(new Map()), clone: vi.fn(), mergeChildState: vi.fn(), isTransformationEnabled: vi.fn().mockReturnValue(true) } as unknown as IStateService; clonedState = { setTextVar: vi.fn(), setDataVar: vi.fn(), setPathVar: vi.fn(), setCommand: vi.fn(), getTextVar: vi.fn(), getDataVar: vi.fn(), getPathVar: vi.fn(), getCommand: vi.fn(), getAllTextVars: vi.fn().mockReturnValue(new Map()), getAllDataVars: vi.fn().mockReturnValue(new Map()), getAllPathVars: vi.fn().mockReturnValue(new Map()), getAllCommands: vi.fn().mockReturnValue(new Map()), createChildState: vi.fn().mockReturnValue(childState), mergeChildState: vi.fn(), clone: vi.fn(), isTransformationEnabled: vi.fn().mockReturnValue(true) } as unknown as IStateService; stateService = { setTextVar: vi.fn(), setDataVar: vi.fn(), setPathVar: vi.fn(), setCommand: vi.fn(), getTextVar: vi.fn(), getDataVar: vi.fn(), getPathVar: vi.fn(), getCommand: vi.fn(), getAllTextVars: vi.fn().mockReturnValue(new Map()), getAllDataVars: vi.fn().mockReturnValue(new Map()), getAllPathVars: vi.fn().mockReturnValue(new Map()), getAllCommands: vi.fn().mockReturnValue(new Map()), clone: vi.fn().mockReturnValue(clonedState), createChildState: vi.fn().mockReturnValue(childState), isTransformationEnabled: vi.fn().mockReturnValue(true) } as unknown as IStateService; resolutionService = { resolveInContext: vi.fn() } as unknown as IResolutionService; fileSystemService = { exists: vi.fn(), readFile: vi.fn(), dirname: vi.fn().mockReturnValue('/workspace'), join: vi.fn().mockImplementation((...args) => args.join('/')), normalize: vi.fn().mockImplementation(path => path) } as unknown as IFileSystemService; parserService = { parse: vi.fn() } as unknown as IParserService; interpreterService = { interpret: vi.fn().mockResolvedValue(childState) } as unknown as IInterpreterService; circularityService = { beginImport: vi.fn(), endImport: vi.fn() } as unknown as ICircularityService; handler = new ImportDirectiveHandler( validationService, resolutionService, stateService, fileSystemService, parserService, interpreterService, circularityService ); }); describe('transformation behavior', () => { it('should return empty text node when transformation enabled', async () => { // MIGRATION: Using centralized syntax example const example = importDirectiveExamples.atomic.basicImport; const node = await createNodeFromExample(example.code); const context = { currentFilePath: 'test.meld', state: stateService }; // Reset mock call counters vi.clearAllMocks(); // Setup mocks vi.mocked(validationService.validate).mockResolvedValue(undefined); vi.mocked(resolutionService.resolveInContext).mockResolvedValue('test.meld'); vi.mocked(fileSystemService.exists).mockResolvedValue(true); vi.mocked(fileSystemService.readFile).mockResolvedValue('test content'); vi.mocked(parserService.parse).mockResolvedValue([]); vi.mocked(childState.getAllTextVars).mockReturnValue(new Map([['var1', 'value1']])); // Execute the handler const result = await handler.execute(node, context); // Verify the result has the expected structure expect(result.replacement).toBeDefined(); expect(result.replacement).toEqual({ type: 'Text', content: '', location: expect.objectContaining({ start: expect.anything(), end: expect.anything() }) }); // Verify the behavior - not the exact state reference // Just check that we have a state expect(result.state).toBeDefined(); }); it('should still import variables when transformation enabled', async () => { // MIGRATION: Let's revert to using direct node creation for now since the particular // format of the importList is important for this test const node: DirectiveNode = { type: 'Directive', directive: { kind: 'import', path: 'test.meld', importList: 'myVar' }, location: createLocation(1, 1) }; const context = { currentFilePath: 'test.meld', state: stateService }; // Reset mock call counters vi.clearAllMocks(); // Setup mocks vi.mocked(validationService.validate).mockResolvedValue(undefined); vi.mocked(resolutionService.resolveInContext).mockResolvedValue('test.meld'); vi.mocked(fileSystemService.exists).mockResolvedValue(true); vi.mocked(fileSystemService.readFile).mockResolvedValue('test content'); vi.mocked(parserService.parse).mockResolvedValue([]); vi.mocked(childState.getTextVar).mockReturnValue('value1'); // Execute the handler const result = await handler.execute(node, context); // Verify the result has the expected structure expect(result.replacement).toBeDefined(); expect(result.replacement).toEqual({ type: 'Text', content: '', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 }, filePath: undefined } }); // Verify the behavior - not the exact state reference // Just check that we have a state expect(result.state).toBeDefined(); }); it('should handle aliased imports in transformation mode', async () => { // MIGRATION: Using direct node creation for consistent behavior with importList format const node: DirectiveNode = { type: 'Directive', directive: { kind: 'import', path: 'test.meld', importList: 'sourceVar:targetVar' }, location: createLocation(1, 1) }; const context = { currentFilePath: 'test.meld', state: stateService }; // Reset mock call counters vi.clearAllMocks(); // Setup mocks vi.mocked(validationService.validate).mockResolvedValue(undefined); vi.mocked(resolutionService.resolveInContext).mockResolvedValue('test.meld'); vi.mocked(fileSystemService.exists).mockResolvedValue(true); vi.mocked(fileSystemService.readFile).mockResolvedValue('test content'); vi.mocked(parserService.parse).mockResolvedValue([]); vi.mocked(childState.getTextVar).mockReturnValue('value1'); // Execute the handler const result = await handler.execute(node, context); // Verify the result has the expected structure expect(result.replacement).toBeDefined(); expect(result.replacement).toEqual({ type: 'Text', content: '', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 }, filePath: undefined } }); // Verify the behavior - not the exact state reference // Just check that we have a state expect(result.state).toBeDefined(); }); it('should preserve error handling in transformation mode', async () => { // MIGRATION: Using centralized syntax example for the file not found case const example = importDirectiveExamples.atomic.basicImport; // Modify the example to use a non-existent file path const modifiedCode = example.code.replace('imported.meld', 'missing.meld'); const node = await createNodeFromExample(modifiedCode); const context = { currentFilePath: 'test.meld', state: stateService }; vi.mocked(validationService.validate).mockResolvedValue(undefined); vi.mocked(resolutionService.resolveInContext).mockResolvedValue('missing.meld'); vi.mocked(fileSystemService.exists).mockResolvedValue(false); // The test expects the handler to throw the error even in transformation mode await expect(handler.execute(node, context)).rejects.toThrow(); expect(circularityService.endImport).toHaveBeenCalled(); }); }); });