@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
248 lines (206 loc) • 7.81 kB
text/typescript
/**
* @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!');
});
});