mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-01 01:05:00 +00:00
feat: implement core test framework with BDD-style functions and lifecycle hooks
This commit is contained in:
144
packages/lua_test/seed/scripts/describe.lua
Normal file
144
packages/lua_test/seed/scripts/describe.lua
Normal file
@@ -0,0 +1,144 @@
|
||||
-- BDD-style test definition functions
|
||||
-- Provides describe, it, xit, fit and parameterized variants
|
||||
|
||||
local suite = require("suite")
|
||||
|
||||
---@class DescribeModule
|
||||
local M = {}
|
||||
|
||||
---Describe block - groups related tests
|
||||
---@param name string Name of the test group
|
||||
---@param fn function Function containing test definitions
|
||||
---@return TestSuite The created test suite
|
||||
function M.describe(name, fn)
|
||||
local parentSuite = suite.getCurrentSuite()
|
||||
local newSuite = suite.createSuite(name)
|
||||
newSuite.parent = parentSuite
|
||||
|
||||
if parentSuite then
|
||||
parentSuite.nested[#parentSuite.nested + 1] = newSuite
|
||||
else
|
||||
suite.registerSuite(newSuite)
|
||||
end
|
||||
|
||||
suite.setCurrentSuite(newSuite)
|
||||
fn()
|
||||
suite.setCurrentSuite(parentSuite)
|
||||
|
||||
return newSuite
|
||||
end
|
||||
|
||||
---It block - defines a single test
|
||||
---@param name string Name of the test
|
||||
---@param fn function Test function to execute
|
||||
---@return Test The created test
|
||||
function M.it(name, fn)
|
||||
local currentSuite = suite.getCurrentSuite()
|
||||
if not currentSuite then
|
||||
error("it() must be called inside a describe() block")
|
||||
end
|
||||
|
||||
local test = {
|
||||
name = name,
|
||||
fn = fn,
|
||||
status = "pending",
|
||||
error = nil,
|
||||
duration = 0,
|
||||
skipped = false
|
||||
}
|
||||
|
||||
currentSuite.tests[#currentSuite.tests + 1] = test
|
||||
return test
|
||||
end
|
||||
|
||||
---Parameterized tests - it.each(cases)(name, fn)
|
||||
---@param cases table[] Array of test case objects
|
||||
---@return ParameterizedTestFactory Factory function accepting name template and test function
|
||||
function M.it_each(cases)
|
||||
return function(nameTemplate, fn)
|
||||
for _, testCase in ipairs(cases) do
|
||||
-- Interpolate $fieldName in the name template
|
||||
local name = nameTemplate
|
||||
for key, value in pairs(testCase) do
|
||||
local strValue = type(value) == "table" and "[table]" or tostring(value)
|
||||
name = string.gsub(name, "%$" .. key, strValue)
|
||||
end
|
||||
|
||||
-- Create test with the interpolated name
|
||||
M.it(name, function()
|
||||
fn(testCase)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Skip a test - test will be marked as skipped and not executed
|
||||
---@param name string Name of the skipped test
|
||||
---@param fn function Test function (will not be executed)
|
||||
---@return Test The created skipped test
|
||||
function M.xit(name, fn)
|
||||
local currentSuite = suite.getCurrentSuite()
|
||||
if not currentSuite then
|
||||
error("xit() must be called inside a describe() block")
|
||||
end
|
||||
|
||||
local test = {
|
||||
name = name,
|
||||
fn = fn,
|
||||
status = "skipped",
|
||||
error = nil,
|
||||
duration = 0,
|
||||
skipped = true
|
||||
}
|
||||
|
||||
currentSuite.tests[#currentSuite.tests + 1] = test
|
||||
return test
|
||||
end
|
||||
|
||||
---Only run this test (for debugging) - all other tests in suite will be skipped
|
||||
---@param name string Name of the focused test
|
||||
---@param fn function Test function to execute
|
||||
---@return Test The created focused test
|
||||
function M.fit(name, fn)
|
||||
local test = M.it(name, fn)
|
||||
test.only = true
|
||||
return test
|
||||
end
|
||||
|
||||
---Parameterized focused tests - fit.each(cases)(name, fn)
|
||||
---@param cases table[] Array of test case objects
|
||||
---@return ParameterizedTestFactory Factory function for focused parameterized tests
|
||||
function M.fit_each(cases)
|
||||
return function(nameTemplate, fn)
|
||||
for _, testCase in ipairs(cases) do
|
||||
local name = nameTemplate
|
||||
for key, value in pairs(testCase) do
|
||||
local strValue = type(value) == "table" and "[table]" or tostring(value)
|
||||
name = string.gsub(name, "%$" .. key, strValue)
|
||||
end
|
||||
M.fit(name, function()
|
||||
fn(testCase)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Parameterized skipped tests - xit.each(cases)(name, fn)
|
||||
---@param cases table[] Array of test case objects
|
||||
---@return ParameterizedTestFactory Factory function for skipped parameterized tests
|
||||
function M.xit_each(cases)
|
||||
return function(nameTemplate, fn)
|
||||
for _, testCase in ipairs(cases) do
|
||||
local name = nameTemplate
|
||||
for key, value in pairs(testCase) do
|
||||
local strValue = type(value) == "table" and "[table]" or tostring(value)
|
||||
name = string.gsub(name, "%$" .. key, strValue)
|
||||
end
|
||||
M.xit(name, function()
|
||||
fn(testCase)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,142 +1,31 @@
|
||||
-- Core test framework with describe/it blocks
|
||||
-- Provides BDD-style test organization
|
||||
-- Core test framework - main entry point
|
||||
-- Re-exports all modules for convenient access
|
||||
--
|
||||
-- Split into focused modules:
|
||||
-- json.lua - JSON parser for test case loading
|
||||
-- types.lua - Shared type definitions
|
||||
-- suite.lua - Suite creation and state management
|
||||
-- describe.lua - BDD describe/it/xit/fit functions
|
||||
-- hooks.lua - beforeAll/afterAll/beforeEach/afterEach
|
||||
|
||||
local json = require("json")
|
||||
local suite = require("suite")
|
||||
local describe = require("describe")
|
||||
local hooks = require("hooks")
|
||||
|
||||
---@class TestFramework
|
||||
---@field _suites TestSuite[] Array of registered test suites
|
||||
---@field _currentSuite TestSuite|nil Currently active suite during registration
|
||||
---@field _config TestConfig Framework configuration
|
||||
local M = {}
|
||||
|
||||
-- JSON parser for loading test cases
|
||||
local json = {}
|
||||
-- Re-export JSON parser
|
||||
M.json = json
|
||||
|
||||
function json.decode(str)
|
||||
-- Simple JSON parser for test case loading
|
||||
-- Handles objects, arrays, strings, numbers, booleans, null
|
||||
local pos = 1
|
||||
|
||||
local function skip_whitespace()
|
||||
while pos <= #str and str:sub(pos, pos):match("%s") do
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_string()
|
||||
pos = pos + 1 -- skip opening quote
|
||||
local start = pos
|
||||
while pos <= #str do
|
||||
local c = str:sub(pos, pos)
|
||||
if c == '"' then
|
||||
local result = str:sub(start, pos - 1)
|
||||
pos = pos + 1
|
||||
-- Handle escape sequences
|
||||
result = result:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r")
|
||||
result = result:gsub('\\"', '"'):gsub("\\\\", "\\")
|
||||
return result
|
||||
elseif c == "\\" then
|
||||
pos = pos + 2 -- skip escape sequence
|
||||
else
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
error("Unterminated string")
|
||||
end
|
||||
|
||||
local function parse_number()
|
||||
local start = pos
|
||||
while pos <= #str and str:sub(pos, pos):match("[%d%.eE%+%-]") do
|
||||
pos = pos + 1
|
||||
end
|
||||
return tonumber(str:sub(start, pos - 1))
|
||||
end
|
||||
|
||||
local parse_value -- forward declaration
|
||||
|
||||
local function parse_array()
|
||||
pos = pos + 1 -- skip [
|
||||
local arr = {}
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) == "]" then
|
||||
pos = pos + 1
|
||||
return arr
|
||||
end
|
||||
while true do
|
||||
skip_whitespace()
|
||||
arr[#arr + 1] = parse_value()
|
||||
skip_whitespace()
|
||||
local c = str:sub(pos, pos)
|
||||
if c == "]" then
|
||||
pos = pos + 1
|
||||
return arr
|
||||
elseif c == "," then
|
||||
pos = pos + 1
|
||||
else
|
||||
error("Expected ',' or ']' in array at position " .. pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_object()
|
||||
pos = pos + 1 -- skip {
|
||||
local obj = {}
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) == "}" then
|
||||
pos = pos + 1
|
||||
return obj
|
||||
end
|
||||
while true do
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) ~= '"' then
|
||||
error("Expected string key at position " .. pos)
|
||||
end
|
||||
local key = parse_string()
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) ~= ":" then
|
||||
error("Expected ':' at position " .. pos)
|
||||
end
|
||||
pos = pos + 1
|
||||
skip_whitespace()
|
||||
obj[key] = parse_value()
|
||||
skip_whitespace()
|
||||
local c = str:sub(pos, pos)
|
||||
if c == "}" then
|
||||
pos = pos + 1
|
||||
return obj
|
||||
elseif c == "," then
|
||||
pos = pos + 1
|
||||
else
|
||||
error("Expected ',' or '}' in object at position " .. pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parse_value = function()
|
||||
skip_whitespace()
|
||||
local c = str:sub(pos, pos)
|
||||
if c == '"' then
|
||||
return parse_string()
|
||||
elseif c == "{" then
|
||||
return parse_object()
|
||||
elseif c == "[" then
|
||||
return parse_array()
|
||||
elseif c == "t" and str:sub(pos, pos + 3) == "true" then
|
||||
pos = pos + 4
|
||||
return true
|
||||
elseif c == "f" and str:sub(pos, pos + 4) == "false" then
|
||||
pos = pos + 5
|
||||
return false
|
||||
elseif c == "n" and str:sub(pos, pos + 3) == "null" then
|
||||
pos = pos + 4
|
||||
return nil
|
||||
elseif c:match("[%d%-]") then
|
||||
return parse_number()
|
||||
else
|
||||
error("Unexpected character '" .. c .. "' at position " .. pos)
|
||||
end
|
||||
end
|
||||
|
||||
return parse_value()
|
||||
end
|
||||
|
||||
-- Load test cases from JSON content
|
||||
-- jsonContent: raw JSON string
|
||||
-- path: optional dot-path to nested data (e.g., "login.valid")
|
||||
---Load test cases from JSON content
|
||||
---@param jsonContent string Raw JSON string containing test cases
|
||||
---@param path? string Optional dot-path to nested data (e.g., "login.valid")
|
||||
---@return table[] Array of test case objects
|
||||
function M.load_cases(jsonContent, path)
|
||||
local data = json.decode(jsonContent)
|
||||
|
||||
@@ -153,199 +42,30 @@ function M.load_cases(jsonContent, path)
|
||||
return data or {}
|
||||
end
|
||||
|
||||
-- Test suite state
|
||||
M._suites = {}
|
||||
M._currentSuite = nil
|
||||
M._config = {
|
||||
timeout = 5000,
|
||||
verbose = true,
|
||||
stopOnFirstFailure = false,
|
||||
filter = nil
|
||||
}
|
||||
-- Re-export suite management (with state access)
|
||||
M._suites = suite._suites
|
||||
M._currentSuite = suite._currentSuite
|
||||
M._config = suite._config
|
||||
|
||||
-- Create a new test suite
|
||||
function M.createSuite(name)
|
||||
local suite = {
|
||||
name = name,
|
||||
tests = {},
|
||||
beforeAll = nil,
|
||||
afterAll = nil,
|
||||
beforeEach = nil,
|
||||
afterEach = nil,
|
||||
nested = {},
|
||||
parent = nil
|
||||
}
|
||||
return suite
|
||||
end
|
||||
M.createSuite = suite.createSuite
|
||||
M.configure = suite.configure
|
||||
M.getSuites = suite.getSuites
|
||||
M.reset = suite.reset
|
||||
M.getConfig = suite.getConfig
|
||||
|
||||
-- Describe block - groups related tests
|
||||
function M.describe(name, fn)
|
||||
local parentSuite = M._currentSuite
|
||||
local suite = M.createSuite(name)
|
||||
suite.parent = parentSuite
|
||||
|
||||
if parentSuite then
|
||||
parentSuite.nested[#parentSuite.nested + 1] = suite
|
||||
else
|
||||
M._suites[#M._suites + 1] = suite
|
||||
end
|
||||
|
||||
M._currentSuite = suite
|
||||
fn()
|
||||
M._currentSuite = parentSuite
|
||||
|
||||
return suite
|
||||
end
|
||||
-- Re-export BDD functions
|
||||
M.describe = describe.describe
|
||||
M.it = describe.it
|
||||
M.it_each = describe.it_each
|
||||
M.xit = describe.xit
|
||||
M.fit = describe.fit
|
||||
M.fit_each = describe.fit_each
|
||||
M.xit_each = describe.xit_each
|
||||
|
||||
-- It block - defines a single test
|
||||
function M.it(name, fn)
|
||||
if not M._currentSuite then
|
||||
error("it() must be called inside a describe() block")
|
||||
end
|
||||
|
||||
local test = {
|
||||
name = name,
|
||||
fn = fn,
|
||||
status = "pending",
|
||||
error = nil,
|
||||
duration = 0,
|
||||
skipped = false
|
||||
}
|
||||
|
||||
M._currentSuite.tests[#M._currentSuite.tests + 1] = test
|
||||
return test
|
||||
end
|
||||
|
||||
-- Parameterized tests - it.each(cases)(name, fn)
|
||||
-- cases: array of test case objects
|
||||
-- name: string with $fieldName placeholders for interpolation
|
||||
-- fn: function(testCase) that receives each case
|
||||
function M.it_each(cases)
|
||||
return function(nameTemplate, fn)
|
||||
for _, testCase in ipairs(cases) do
|
||||
-- Interpolate $fieldName in the name template
|
||||
local name = nameTemplate
|
||||
for key, value in pairs(testCase) do
|
||||
local placeholder = "$" .. key
|
||||
local strValue = type(value) == "table" and "[table]" or tostring(value)
|
||||
name = string.gsub(name, "%$" .. key, strValue)
|
||||
end
|
||||
|
||||
-- Create test with the interpolated name
|
||||
M.it(name, function()
|
||||
fn(testCase)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Skip a test
|
||||
function M.xit(name, fn)
|
||||
if not M._currentSuite then
|
||||
error("xit() must be called inside a describe() block")
|
||||
end
|
||||
|
||||
local test = {
|
||||
name = name,
|
||||
fn = fn,
|
||||
status = "skipped",
|
||||
error = nil,
|
||||
duration = 0,
|
||||
skipped = true
|
||||
}
|
||||
|
||||
M._currentSuite.tests[#M._currentSuite.tests + 1] = test
|
||||
return test
|
||||
end
|
||||
|
||||
-- Only run this test (for debugging)
|
||||
function M.fit(name, fn)
|
||||
local test = M.it(name, fn)
|
||||
test.only = true
|
||||
return test
|
||||
end
|
||||
|
||||
-- Parameterized fit - fit.each(cases)(name, fn)
|
||||
function M.fit_each(cases)
|
||||
return function(nameTemplate, fn)
|
||||
for _, testCase in ipairs(cases) do
|
||||
local name = nameTemplate
|
||||
for key, value in pairs(testCase) do
|
||||
local placeholder = "$" .. key
|
||||
local strValue = type(value) == "table" and "[table]" or tostring(value)
|
||||
name = string.gsub(name, "%$" .. key, strValue)
|
||||
end
|
||||
M.fit(name, function()
|
||||
fn(testCase)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Parameterized xit - xit.each(cases)(name, fn)
|
||||
function M.xit_each(cases)
|
||||
return function(nameTemplate, fn)
|
||||
for _, testCase in ipairs(cases) do
|
||||
local name = nameTemplate
|
||||
for key, value in pairs(testCase) do
|
||||
local strValue = type(value) == "table" and "[table]" or tostring(value)
|
||||
name = string.gsub(name, "%$" .. key, strValue)
|
||||
end
|
||||
M.xit(name, function()
|
||||
fn(testCase)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Setup hooks
|
||||
function M.beforeAll(fn)
|
||||
if M._currentSuite then
|
||||
M._currentSuite.beforeAll = fn
|
||||
end
|
||||
end
|
||||
|
||||
function M.afterAll(fn)
|
||||
if M._currentSuite then
|
||||
M._currentSuite.afterAll = fn
|
||||
end
|
||||
end
|
||||
|
||||
function M.beforeEach(fn)
|
||||
if M._currentSuite then
|
||||
M._currentSuite.beforeEach = fn
|
||||
end
|
||||
end
|
||||
|
||||
function M.afterEach(fn)
|
||||
if M._currentSuite then
|
||||
M._currentSuite.afterEach = fn
|
||||
end
|
||||
end
|
||||
|
||||
-- Configure the test framework
|
||||
function M.configure(options)
|
||||
for k, v in pairs(options) do
|
||||
if M._config[k] ~= nil then
|
||||
M._config[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get all registered suites
|
||||
function M.getSuites()
|
||||
return M._suites
|
||||
end
|
||||
|
||||
-- Reset all suites (for fresh test runs)
|
||||
function M.reset()
|
||||
M._suites = {}
|
||||
M._currentSuite = nil
|
||||
end
|
||||
|
||||
-- Get current config
|
||||
function M.getConfig()
|
||||
return M._config
|
||||
end
|
||||
-- Re-export hooks
|
||||
M.beforeAll = hooks.beforeAll
|
||||
M.afterAll = hooks.afterAll
|
||||
M.beforeEach = hooks.beforeEach
|
||||
M.afterEach = hooks.afterEach
|
||||
|
||||
return M
|
||||
|
||||
45
packages/lua_test/seed/scripts/hooks.lua
Normal file
45
packages/lua_test/seed/scripts/hooks.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
-- Test lifecycle hooks
|
||||
-- Provides beforeAll, afterAll, beforeEach, afterEach
|
||||
|
||||
local suite = require("suite")
|
||||
|
||||
---@class HooksModule
|
||||
local M = {}
|
||||
|
||||
---Setup hook run once before all tests in a suite
|
||||
---@param fn function Setup function
|
||||
function M.beforeAll(fn)
|
||||
local currentSuite = suite.getCurrentSuite()
|
||||
if currentSuite then
|
||||
currentSuite.beforeAll = fn
|
||||
end
|
||||
end
|
||||
|
||||
---Teardown hook run once after all tests in a suite
|
||||
---@param fn function Teardown function
|
||||
function M.afterAll(fn)
|
||||
local currentSuite = suite.getCurrentSuite()
|
||||
if currentSuite then
|
||||
currentSuite.afterAll = fn
|
||||
end
|
||||
end
|
||||
|
||||
---Setup hook run before each test in a suite
|
||||
---@param fn function Setup function
|
||||
function M.beforeEach(fn)
|
||||
local currentSuite = suite.getCurrentSuite()
|
||||
if currentSuite then
|
||||
currentSuite.beforeEach = fn
|
||||
end
|
||||
end
|
||||
|
||||
---Teardown hook run after each test in a suite
|
||||
---@param fn function Teardown function
|
||||
function M.afterEach(fn)
|
||||
local currentSuite = suite.getCurrentSuite()
|
||||
if currentSuite then
|
||||
currentSuite.afterEach = fn
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,11 +1,21 @@
|
||||
-- lua_test package initialization
|
||||
-- Unit testing framework for MetaBuilder Lua scripts
|
||||
|
||||
---@class LuaTestModule
|
||||
---@field version string Package version
|
||||
---@field name string Package name
|
||||
local M = {}
|
||||
|
||||
M.version = "1.0.0"
|
||||
M.name = "lua_test"
|
||||
|
||||
---@class LuaTestInitResult
|
||||
---@field success boolean Whether initialization succeeded
|
||||
---@field message string Initialization message
|
||||
---@field version string Package version
|
||||
|
||||
---Initialize the Lua test framework
|
||||
---@return LuaTestInitResult
|
||||
function M.init()
|
||||
return {
|
||||
success = true,
|
||||
@@ -14,6 +24,14 @@ function M.init()
|
||||
}
|
||||
end
|
||||
|
||||
---@class LuaTestInfo
|
||||
---@field name string Package name
|
||||
---@field version string Package version
|
||||
---@field description string Package description
|
||||
---@field features string[] List of framework features
|
||||
|
||||
---Get information about the test framework
|
||||
---@return LuaTestInfo
|
||||
function M.info()
|
||||
return {
|
||||
name = M.name,
|
||||
|
||||
136
packages/lua_test/seed/scripts/json.lua
Normal file
136
packages/lua_test/seed/scripts/json.lua
Normal file
@@ -0,0 +1,136 @@
|
||||
-- JSON parser for loading test cases
|
||||
-- Lightweight parser for test case data
|
||||
|
||||
---@class JSONParser
|
||||
local M = {}
|
||||
|
||||
---Decode a JSON string into a Lua value
|
||||
---@param str string JSON string to parse
|
||||
---@return any Parsed value (table, string, number, boolean, or nil)
|
||||
function M.decode(str)
|
||||
local pos = 1
|
||||
|
||||
local function skip_whitespace()
|
||||
while pos <= #str and str:sub(pos, pos):match("%s") do
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_string()
|
||||
pos = pos + 1 -- skip opening quote
|
||||
local start = pos
|
||||
while pos <= #str do
|
||||
local c = str:sub(pos, pos)
|
||||
if c == '"' then
|
||||
local result = str:sub(start, pos - 1)
|
||||
pos = pos + 1
|
||||
-- Handle escape sequences
|
||||
result = result:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r")
|
||||
result = result:gsub('\\"', '"'):gsub("\\\\", "\\")
|
||||
return result
|
||||
elseif c == "\\" then
|
||||
pos = pos + 2 -- skip escape sequence
|
||||
else
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
error("Unterminated string")
|
||||
end
|
||||
|
||||
local function parse_number()
|
||||
local start = pos
|
||||
while pos <= #str and str:sub(pos, pos):match("[%d%.eE%+%-]") do
|
||||
pos = pos + 1
|
||||
end
|
||||
return tonumber(str:sub(start, pos - 1))
|
||||
end
|
||||
|
||||
local parse_value -- forward declaration
|
||||
|
||||
local function parse_array()
|
||||
pos = pos + 1 -- skip [
|
||||
local arr = {}
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) == "]" then
|
||||
pos = pos + 1
|
||||
return arr
|
||||
end
|
||||
while true do
|
||||
skip_whitespace()
|
||||
arr[#arr + 1] = parse_value()
|
||||
skip_whitespace()
|
||||
local c = str:sub(pos, pos)
|
||||
if c == "]" then
|
||||
pos = pos + 1
|
||||
return arr
|
||||
elseif c == "," then
|
||||
pos = pos + 1
|
||||
else
|
||||
error("Expected ',' or ']' in array at position " .. pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_object()
|
||||
pos = pos + 1 -- skip {
|
||||
local obj = {}
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) == "}" then
|
||||
pos = pos + 1
|
||||
return obj
|
||||
end
|
||||
while true do
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) ~= '"' then
|
||||
error("Expected string key at position " .. pos)
|
||||
end
|
||||
local key = parse_string()
|
||||
skip_whitespace()
|
||||
if str:sub(pos, pos) ~= ":" then
|
||||
error("Expected ':' at position " .. pos)
|
||||
end
|
||||
pos = pos + 1
|
||||
skip_whitespace()
|
||||
obj[key] = parse_value()
|
||||
skip_whitespace()
|
||||
local c = str:sub(pos, pos)
|
||||
if c == "}" then
|
||||
pos = pos + 1
|
||||
return obj
|
||||
elseif c == "," then
|
||||
pos = pos + 1
|
||||
else
|
||||
error("Expected ',' or '}' in object at position " .. pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parse_value = function()
|
||||
skip_whitespace()
|
||||
local c = str:sub(pos, pos)
|
||||
if c == '"' then
|
||||
return parse_string()
|
||||
elseif c == "{" then
|
||||
return parse_object()
|
||||
elseif c == "[" then
|
||||
return parse_array()
|
||||
elseif c == "t" and str:sub(pos, pos + 3) == "true" then
|
||||
pos = pos + 4
|
||||
return true
|
||||
elseif c == "f" and str:sub(pos, pos + 4) == "false" then
|
||||
pos = pos + 5
|
||||
return false
|
||||
elseif c == "n" and str:sub(pos, pos + 3) == "null" then
|
||||
pos = pos + 4
|
||||
return nil
|
||||
elseif c:match("[%d%-]") then
|
||||
return parse_number()
|
||||
else
|
||||
error("Unexpected character '" .. c .. "' at position " .. pos)
|
||||
end
|
||||
end
|
||||
|
||||
return parse_value()
|
||||
end
|
||||
|
||||
return M
|
||||
83
packages/lua_test/seed/scripts/suite.lua
Normal file
83
packages/lua_test/seed/scripts/suite.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
-- Test suite state and management
|
||||
-- Handles suite creation, registration, and configuration
|
||||
|
||||
---@class SuiteManager
|
||||
---@field _suites TestSuite[] Array of registered test suites
|
||||
---@field _currentSuite TestSuite|nil Currently active suite during registration
|
||||
---@field _config TestConfig Framework configuration
|
||||
local M = {}
|
||||
|
||||
-- Test suite state
|
||||
M._suites = {}
|
||||
M._currentSuite = nil
|
||||
M._config = {
|
||||
timeout = 5000,
|
||||
verbose = true,
|
||||
stopOnFirstFailure = false,
|
||||
filter = nil
|
||||
}
|
||||
|
||||
---Create a new test suite
|
||||
---@param name string Name of the test suite
|
||||
---@return TestSuite
|
||||
function M.createSuite(name)
|
||||
local suite = {
|
||||
name = name,
|
||||
tests = {},
|
||||
beforeAll = nil,
|
||||
afterAll = nil,
|
||||
beforeEach = nil,
|
||||
afterEach = nil,
|
||||
nested = {},
|
||||
parent = nil
|
||||
}
|
||||
return suite
|
||||
end
|
||||
|
||||
---Register a suite with the framework
|
||||
---@param suite TestSuite The suite to register
|
||||
function M.registerSuite(suite)
|
||||
M._suites[#M._suites + 1] = suite
|
||||
end
|
||||
|
||||
---Get the current suite being defined
|
||||
---@return TestSuite|nil
|
||||
function M.getCurrentSuite()
|
||||
return M._currentSuite
|
||||
end
|
||||
|
||||
---Set the current suite being defined
|
||||
---@param suite TestSuite|nil
|
||||
function M.setCurrentSuite(suite)
|
||||
M._currentSuite = suite
|
||||
end
|
||||
|
||||
---Configure the test framework
|
||||
---@param options TestConfig Configuration options to set
|
||||
function M.configure(options)
|
||||
for k, v in pairs(options) do
|
||||
if M._config[k] ~= nil then
|
||||
M._config[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Get all registered test suites
|
||||
---@return TestSuite[] Array of registered suites
|
||||
function M.getSuites()
|
||||
return M._suites
|
||||
end
|
||||
|
||||
---Reset all suites and state for a fresh test run
|
||||
function M.reset()
|
||||
M._suites = {}
|
||||
M._currentSuite = nil
|
||||
end
|
||||
|
||||
---Get current test configuration
|
||||
---@return TestConfig Current configuration
|
||||
function M.getConfig()
|
||||
return M._config
|
||||
end
|
||||
|
||||
return M
|
||||
33
packages/lua_test/seed/scripts/types.lua
Normal file
33
packages/lua_test/seed/scripts/types.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Shared type definitions for the test framework
|
||||
-- Central location for all @class and @alias definitions
|
||||
|
||||
---@class TestConfig
|
||||
---@field timeout number Test timeout in milliseconds
|
||||
---@field verbose boolean Enable verbose output
|
||||
---@field stopOnFirstFailure boolean Stop suite on first test failure
|
||||
---@field filter string|nil Filter tests by name pattern
|
||||
|
||||
---@class TestSuite
|
||||
---@field name string Suite name
|
||||
---@field tests Test[] Array of tests in this suite
|
||||
---@field beforeAll function|nil Hook run once before all tests
|
||||
---@field afterAll function|nil Hook run once after all tests
|
||||
---@field beforeEach function|nil Hook run before each test
|
||||
---@field afterEach function|nil Hook run after each test
|
||||
---@field nested TestSuite[] Nested test suites
|
||||
---@field parent TestSuite|nil Parent suite if nested
|
||||
|
||||
---@class Test
|
||||
---@field name string Test name
|
||||
---@field fn function Test function to execute
|
||||
---@field status string Test status (pending, passed, failed, skipped)
|
||||
---@field error string|nil Error message if failed
|
||||
---@field duration number Test duration in milliseconds
|
||||
---@field skipped boolean Whether test is skipped
|
||||
---@field only boolean|nil If true, only this test runs
|
||||
|
||||
---@alias ParameterizedTestFactory fun(nameTemplate: string, fn: fun(testCase: table)): nil
|
||||
|
||||
-- This module only provides type definitions for LuaLS
|
||||
-- No runtime exports needed
|
||||
return {}
|
||||
Reference in New Issue
Block a user