mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
feat(types): introduce builder and level types for component and user roles feat(workflow): implement workflow engine with execution context and logging style(theme): create modular theme exports for consistent styling chore(tests): add comprehensive tests for workflow execution and Lua nodes
358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { LuaEngine, createLuaEngine, type LuaExecutionContext } from './lua-engine'
|
|
|
|
describe('lua-engine', () => {
|
|
let engine: LuaEngine
|
|
|
|
beforeEach(() => {
|
|
engine = createLuaEngine()
|
|
})
|
|
|
|
afterEach(() => {
|
|
engine.destroy()
|
|
})
|
|
|
|
describe('createLuaEngine', () => {
|
|
it('should create a new LuaEngine instance', () => {
|
|
const newEngine = createLuaEngine()
|
|
expect(newEngine).toBeInstanceOf(LuaEngine)
|
|
newEngine.destroy()
|
|
})
|
|
})
|
|
|
|
describe('execute', () => {
|
|
describe('basic operations', () => {
|
|
it.each([
|
|
{ name: 'return number', code: 'return 42', expected: 42 },
|
|
{ name: 'return string', code: 'return "hello"', expected: 'hello' },
|
|
{ name: 'return boolean true', code: 'return true', expected: true },
|
|
{ name: 'return boolean false', code: 'return false', expected: false },
|
|
{ name: 'return nil', code: 'return nil', expected: null },
|
|
{ name: 'arithmetic', code: 'return 10 + 5 * 2', expected: 20 },
|
|
{ name: 'string concatenation', code: 'return "hello" .. " " .. "world"', expected: 'hello world' },
|
|
{ name: 'comparison', code: 'return 5 > 3', expected: true },
|
|
])('should execute $name', async ({ code, expected }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBe(expected)
|
|
expect(result.error).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('table handling', () => {
|
|
it.each([
|
|
{
|
|
name: 'array-like table returns object with numeric keys',
|
|
code: 'return {1, 2, 3, 4, 5}',
|
|
// Note: Lua tables with numeric keys are returned as objects with string keys
|
|
// This is a known limitation of the current implementation
|
|
expected: { '1': 1, '2': 2, '3': 3, '4': 4, '5': 5 },
|
|
},
|
|
{
|
|
name: 'object-like table',
|
|
code: 'return {name = "Alice", age = 30}',
|
|
expected: { name: 'Alice', age: 30 },
|
|
},
|
|
{
|
|
name: 'nested table',
|
|
code: 'return {user = {name = "Bob"}, count = 1}',
|
|
expected: { user: { name: 'Bob' }, count: 1 },
|
|
},
|
|
{
|
|
name: 'mixed table with nested numeric keys',
|
|
code: 'return {items = {1, 2, 3}, total = 6}',
|
|
// Nested array-like tables also become objects with numeric string keys
|
|
expected: { items: { '1': 1, '2': 2, '3': 3 }, total: 6 },
|
|
},
|
|
{
|
|
name: 'empty table',
|
|
code: 'return {}',
|
|
expected: {},
|
|
},
|
|
])('should handle $name', async ({ code, expected }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toEqual(expected)
|
|
})
|
|
})
|
|
|
|
describe('context data', () => {
|
|
it.each([
|
|
{
|
|
name: 'access context.data number',
|
|
code: 'return context.data.value * 2',
|
|
context: { data: { value: 21 } },
|
|
expected: 42,
|
|
},
|
|
{
|
|
name: 'access context.data string',
|
|
code: 'return context.data.name',
|
|
context: { data: { name: 'test' } },
|
|
expected: 'test',
|
|
},
|
|
{
|
|
name: 'access context.data boolean',
|
|
code: 'return context.data.active',
|
|
context: { data: { active: true } },
|
|
expected: true,
|
|
},
|
|
{
|
|
name: 'access nested context.data',
|
|
code: 'return context.data.user.name',
|
|
context: { data: { user: { name: 'Alice' } } },
|
|
expected: 'Alice',
|
|
},
|
|
{
|
|
name: 'access context.data array',
|
|
code: 'return context.data.items[2]',
|
|
context: { data: { items: [10, 20, 30] } },
|
|
expected: 20,
|
|
},
|
|
])('should $name', async ({ code, context, expected }) => {
|
|
const result = await engine.execute(code, context)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBe(expected)
|
|
})
|
|
|
|
it('should access context.user', async () => {
|
|
const result = await engine.execute(
|
|
'return context.user.id',
|
|
{ user: { id: 'user123', name: 'Test' } }
|
|
)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBe('user123')
|
|
})
|
|
})
|
|
|
|
describe('functions', () => {
|
|
it.each([
|
|
{
|
|
name: 'simple function',
|
|
code: `
|
|
function add(a, b)
|
|
return a + b
|
|
end
|
|
return add(3, 4)
|
|
`,
|
|
expected: 7,
|
|
},
|
|
{
|
|
name: 'recursive function',
|
|
code: `
|
|
function factorial(n)
|
|
if n <= 1 then return 1 end
|
|
return n * factorial(n - 1)
|
|
end
|
|
return factorial(5)
|
|
`,
|
|
expected: 120,
|
|
},
|
|
{
|
|
name: 'higher-order function',
|
|
code: `
|
|
function apply(f, x)
|
|
return f(x)
|
|
end
|
|
function double(n)
|
|
return n * 2
|
|
end
|
|
return apply(double, 10)
|
|
`,
|
|
expected: 20,
|
|
},
|
|
{
|
|
name: 'closure',
|
|
code: `
|
|
function counter()
|
|
local count = 0
|
|
return function()
|
|
count = count + 1
|
|
return count
|
|
end
|
|
end
|
|
local c = counter()
|
|
c()
|
|
c()
|
|
return c()
|
|
`,
|
|
expected: 3,
|
|
},
|
|
])('should execute $name', async ({ code, expected }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBe(expected)
|
|
})
|
|
})
|
|
|
|
describe('control flow', () => {
|
|
it.each([
|
|
{
|
|
name: 'if-then-else true branch',
|
|
code: `
|
|
local x = 10
|
|
if x > 5 then
|
|
return "big"
|
|
else
|
|
return "small"
|
|
end
|
|
`,
|
|
expected: 'big',
|
|
},
|
|
{
|
|
name: 'if-then-else false branch',
|
|
code: `
|
|
local x = 3
|
|
if x > 5 then
|
|
return "big"
|
|
else
|
|
return "small"
|
|
end
|
|
`,
|
|
expected: 'small',
|
|
},
|
|
{
|
|
name: 'for loop',
|
|
code: `
|
|
local sum = 0
|
|
for i = 1, 5 do
|
|
sum = sum + i
|
|
end
|
|
return sum
|
|
`,
|
|
expected: 15,
|
|
},
|
|
{
|
|
name: 'while loop',
|
|
code: `
|
|
local count = 0
|
|
local n = 10
|
|
while n > 0 do
|
|
count = count + 1
|
|
n = n - 2
|
|
end
|
|
return count
|
|
`,
|
|
expected: 5,
|
|
},
|
|
])('should execute $name', async ({ code, expected }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBe(expected)
|
|
})
|
|
})
|
|
|
|
describe('logging', () => {
|
|
it('should capture log() calls', async () => {
|
|
const result = await engine.execute(`
|
|
log("message 1")
|
|
log("message 2")
|
|
return "done"
|
|
`)
|
|
expect(result.success).toBe(true)
|
|
expect(result.logs).toContain('message 1')
|
|
expect(result.logs).toContain('message 2')
|
|
})
|
|
|
|
it('should capture print() calls', async () => {
|
|
const result = await engine.execute(`
|
|
print("printed output")
|
|
return true
|
|
`)
|
|
expect(result.success).toBe(true)
|
|
expect(result.logs).toContain('printed output')
|
|
})
|
|
|
|
it.each([
|
|
{ name: 'number', code: 'log(42)', expected: '42' },
|
|
{ name: 'boolean', code: 'log(true)', expected: 'true' },
|
|
{ name: 'multiple args', code: 'log("a", "b", "c")', expected: 'a b c' },
|
|
])('should log $name correctly', async ({ code, expected }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.logs).toContain(expected)
|
|
})
|
|
})
|
|
|
|
describe('error handling', () => {
|
|
it.each([
|
|
{
|
|
name: 'syntax error',
|
|
code: 'this is not valid lua !!!',
|
|
errorContains: 'Syntax error',
|
|
},
|
|
{
|
|
name: 'undefined variable',
|
|
code: 'return undefinedVar.property',
|
|
errorContains: 'Runtime error',
|
|
},
|
|
{
|
|
name: 'type error',
|
|
code: 'return "string" + 5',
|
|
errorContains: 'error',
|
|
},
|
|
{
|
|
name: 'explicit error',
|
|
code: 'error("intentional error")',
|
|
errorContains: 'intentional error',
|
|
},
|
|
])('should handle $name', async ({ code, errorContains }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.success).toBe(false)
|
|
expect(result.error?.toLowerCase()).toContain(errorContains.toLowerCase())
|
|
})
|
|
})
|
|
|
|
describe('multiple return values', () => {
|
|
it('should handle multiple return values', async () => {
|
|
const result = await engine.execute('return 1, 2, 3')
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toEqual([1, 2, 3])
|
|
})
|
|
|
|
it('should handle function with multiple returns', async () => {
|
|
const result = await engine.execute(`
|
|
function swap(a, b)
|
|
return b, a
|
|
end
|
|
return swap(1, 2)
|
|
`)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toEqual([2, 1])
|
|
})
|
|
})
|
|
|
|
describe('no return value', () => {
|
|
it('should handle code with no return', async () => {
|
|
const result = await engine.execute('local x = 1 + 1')
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('standard library', () => {
|
|
it.each([
|
|
{ name: 'math.floor', code: 'return math.floor(3.7)', expected: 3 },
|
|
{ name: 'math.ceil', code: 'return math.ceil(3.2)', expected: 4 },
|
|
{ name: 'math.abs', code: 'return math.abs(-5)', expected: 5 },
|
|
{ name: 'math.max', code: 'return math.max(1, 5, 3)', expected: 5 },
|
|
{ name: 'math.min', code: 'return math.min(1, 5, 3)', expected: 1 },
|
|
{ name: 'string.upper', code: 'return string.upper("hello")', expected: 'HELLO' },
|
|
{ name: 'string.lower', code: 'return string.lower("HELLO")', expected: 'hello' },
|
|
{ name: 'string.len', code: 'return string.len("test")', expected: 4 },
|
|
{ name: 'string.sub', code: 'return string.sub("hello", 2, 4)', expected: 'ell' },
|
|
{ name: 'table.concat', code: 'return table.concat({"a", "b", "c"}, ", ")', expected: 'a, b, c' },
|
|
])('should support $name', async ({ code, expected }) => {
|
|
const result = await engine.execute(code)
|
|
expect(result.success).toBe(true)
|
|
expect(result.result).toBe(expected)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('destroy', () => {
|
|
it('should cleanup without error', () => {
|
|
const testEngine = createLuaEngine()
|
|
expect(() => testEngine.destroy()).not.toThrow()
|
|
})
|
|
})
|
|
})
|