mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
test: add comprehensive lua-engine tests (51 parameterized cases)
- Add tests for basic Lua operations (numbers, strings, booleans) - Add tests for table handling and context data access - Add tests for function execution (simple, recursive, closures) - Add tests for control flow (if/else, for/while loops) - Add tests for logging (log/print capture) - Add tests for error handling (syntax, runtime, explicit) - Add tests for multiple return values and standard library - Coverage improved from 62% to 89% for lua-engine.ts - Overall coverage now at 83% (up from 63%)
This commit is contained in:
357
frontends/nextjs/src/lib/lua-engine.test.ts
Normal file
357
frontends/nextjs/src/lib/lua-engine.test.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user