UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

248 lines (206 loc) 7.81 kB
/** * @fileoverview Comprehensive parser tests */ import { describe, expect, it } from 'vitest'; import { DirectiveType, ExpressionType, LifecycleType } from '../types/index.js'; import { OrdoJSLexer } from './lexer.js'; import { OrdoJSParser } from './parser.js'; describe('OrdoJSParser - Comprehensive Tests', () => { const parseComponent = (source: string) => { const lexer = new OrdoJSLexer(source, 'test.ordo'); const tokens = lexer.tokenize(); const parser = new OrdoJSParser(tokens, {}, 'test.ordo'); return parser.parse(); }; it('should parse complex component with all features', () => { const source = ` component TodoApp(initialTodos: Todo[] = []) { client { let todos = initialTodos; let newTodo = ""; const filter = "all"; onMount() { loadTodos(); } addTodo(): void { if (newTodo.trim()) { todos = [...todos, { id: Date.now(), text: newTodo.trim(), completed: false }]; newTodo = ""; } } } server { public async loadTodos(): Promise<Todo[]> { return await db.todos.findMany(); } } markup { <div class="todo-app"> <header> <h1>Todo App</h1> <input bind:value={newTodo} on:keydown={handleKeydown} placeholder="Add a new todo..." /> <button on:click={addTodo}>Add</button> </header> <main> <ul class="todo-list"> <li> <span>{todo.text}</span> <button on:click={removeTodo}>×</button> </li> </ul> </main> </div> } } `; const ast = parseComponent(source); // Verify component structure expect(ast.component.name).toBe('TodoApp'); expect(ast.component.props).toHaveLength(1); expect(ast.component.props[0].name).toBe('initialTodos'); expect(ast.component.props[0].dataType.name).toBe('Todo'); expect(ast.component.props[0].dataType.isArray).toBe(true); // Verify client block const clientBlock = ast.component.clientBlock!; expect(clientBlock).toBeDefined(); expect(clientBlock.reactiveVariables).toHaveLength(3); expect(clientBlock.lifecycle).toHaveLength(1); expect(clientBlock.functions).toHaveLength(1); // Verify reactive variables expect(clientBlock.reactiveVariables[0].name).toBe('todos'); expect(clientBlock.reactiveVariables[0].isConst).toBe(false); expect(clientBlock.reactiveVariables[1].name).toBe('newTodo'); expect(clientBlock.reactiveVariables[2].name).toBe('filter'); expect(clientBlock.reactiveVariables[2].isConst).toBe(true); // Verify lifecycle hooks expect(clientBlock.lifecycle[0].hookType).toBe(LifecycleType.ON_MOUNT); // Verify functions expect(clientBlock.functions[0].name).toBe('addTodo'); expect(clientBlock.functions[0].returnType.name).toBe('void'); // Verify server block const serverBlock = ast.component.serverBlock!; expect(serverBlock).toBeDefined(); expect(serverBlock.functions).toHaveLength(1); expect(serverBlock.functions[0].name).toBe('loadTodos'); expect(serverBlock.functions[0].isPublic).toBe(true); // Verify markup block const markupBlock = ast.component.markupBlock; expect(markupBlock).toBeDefined(); expect(markupBlock.elements).toHaveLength(1); // Verify main div element const mainDiv = markupBlock.elements[0]; expect(mainDiv.tagName).toBe('div'); expect(mainDiv.attributes).toHaveLength(1); expect(mainDiv.attributes[0].name).toBe('class'); expect(mainDiv.attributes[0].value).toBe('todo-app'); // Verify nested structure expect(mainDiv.children).toHaveLength(2); // header and main console.log('✅ Complex component parsing successful!'); }); it('should parse HTML with directives and interpolations', () => { const source = ` component TestComponent { markup { <div> <input bind:value={inputValue} /> <button on:click={handleClick}>Click: {count}</button> <span>{user.name}</span> </div> } } `; const ast = parseComponent(source); const markupBlock = ast.component.markupBlock; const mainDiv = markupBlock.elements[0]; // Find input element const inputElement = mainDiv.children.find(child => child.type === 'HTMLElement' && (child as any).tagName === 'input' ) as any; expect(inputElement).toBeDefined(); expect(inputElement.attributes[0].isDirective).toBe(true); expect(inputElement.attributes[0].directiveType).toBe(DirectiveType.BIND); // Find button element const buttonElement = mainDiv.children.find(child => child.type === 'HTMLElement' && (child as any).tagName === 'button' ) as any; expect(buttonElement).toBeDefined(); expect(buttonElement.attributes[0].isDirective).toBe(true); expect(buttonElement.attributes[0].directiveType).toBe(DirectiveType.ON); // Verify interpolations expect(markupBlock.interpolations.length).toBeGreaterThan(0); console.log('✅ HTML directives and interpolations parsing successful!'); }); it('should handle nested HTML elements correctly', () => { const source = ` component TestComponent { markup { <div class="container"> <header> <h1>Title</h1> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> </ul> </nav> </header> <main> <p>Content goes here</p> </main> </div> } } `; const ast = parseComponent(source); const markupBlock = ast.component.markupBlock; const containerDiv = markupBlock.elements[0]; expect(containerDiv.tagName).toBe('div'); expect(containerDiv.children).toHaveLength(2); // header and main // Verify nested structure const header = containerDiv.children[0] as any; expect(header.tagName).toBe('header'); expect(header.children).toHaveLength(2); // h1 and nav const nav = header.children[1] as any; expect(nav.tagName).toBe('nav'); const ul = nav.children[0] as any; expect(ul.tagName).toBe('ul'); expect(ul.children).toHaveLength(2); // two li elements console.log('✅ Nested HTML elements parsing successful!'); }); it('should parse expressions correctly', () => { const source = ` component TestComponent { client { let result = a + b * c - d / e; let comparison = x > y && z < w; let assignment = count++; let call = Math.max(a, b, c); let member = user.profile.name; } markup { <div>Test</div> } } `; const ast = parseComponent(source); const clientBlock = ast.component.clientBlock!; const variables = clientBlock.reactiveVariables; // Test binary expression expect(variables[0].initialValue.expressionType).toBe(ExpressionType.BINARY); // Test logical expression expect(variables[1].initialValue.expressionType).toBe(ExpressionType.BINARY); // Test call expression expect(variables[3].initialValue.expressionType).toBe(ExpressionType.CALL); // Test member expression expect(variables[4].initialValue.expressionType).toBe(ExpressionType.MEMBER); console.log('✅ Expression parsing successful!'); }); });