Add ModuleLoader for conditional module loading with graceful fallbacks
- Create ModuleLoader.lua with safeRequire() for optional module loading - Implement null-object pattern for missing optional modules - Update FlexLove.lua to use ModuleLoader for Performance, Animation, Blur, Theme, ImageRenderer, ImageScaler, ImageCache, NinePatch, and GestureRecognizer - Add comprehensive test suite for ModuleLoader (18 tests) - Validate FlexLove works correctly when optional modules are missing - All tests pass (1253/1254 successes)
This commit is contained in:
116
FlexLove.lua
116
FlexLove.lua
@@ -3,23 +3,28 @@ local function req(name)
|
|||||||
return require(modulePath .. "modules." .. name)
|
return require(modulePath .. "modules." .. name)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- internals
|
-- Load ErrorHandler first (required for ModuleLoader)
|
||||||
local Blur = req("Blur")
|
---@type ErrorHandler
|
||||||
|
local ErrorHandler = req("ErrorHandler")
|
||||||
|
|
||||||
|
-- Load ModuleLoader
|
||||||
|
local ModuleLoader = req("ModuleLoader")
|
||||||
|
ModuleLoader.init({ ErrorHandler = ErrorHandler })
|
||||||
|
|
||||||
|
-- Helper function for safe module loading
|
||||||
|
local function safeReq(name, isOptional)
|
||||||
|
return ModuleLoader.safeRequire(modulePath .. "modules." .. name, isOptional)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Required core modules
|
||||||
local utils = req("utils")
|
local utils = req("utils")
|
||||||
local Units = req("Units")
|
local Units = req("Units")
|
||||||
local Context = req("Context")
|
local Context = req("Context")
|
||||||
---@type StateManager
|
---@type StateManager
|
||||||
local StateManager = req("StateManager")
|
local StateManager = req("StateManager")
|
||||||
---@type Performance
|
|
||||||
local Performance = req("Performance")
|
|
||||||
local ImageRenderer = req("ImageRenderer")
|
|
||||||
local ImageScaler = req("ImageScaler")
|
|
||||||
local NinePatch = req("NinePatch")
|
|
||||||
local RoundedRect = req("RoundedRect")
|
local RoundedRect = req("RoundedRect")
|
||||||
local ImageCache = req("ImageCache")
|
|
||||||
local Grid = req("Grid")
|
local Grid = req("Grid")
|
||||||
local InputEvent = req("InputEvent")
|
local InputEvent = req("InputEvent")
|
||||||
local GestureRecognizer = req("GestureRecognizer")
|
|
||||||
local TextEditor = req("TextEditor")
|
local TextEditor = req("TextEditor")
|
||||||
---@type LayoutEngine
|
---@type LayoutEngine
|
||||||
local LayoutEngine = req("LayoutEngine")
|
local LayoutEngine = req("LayoutEngine")
|
||||||
@@ -27,19 +32,28 @@ local Renderer = req("Renderer")
|
|||||||
---@type EventHandler
|
---@type EventHandler
|
||||||
local EventHandler = req("EventHandler")
|
local EventHandler = req("EventHandler")
|
||||||
local ScrollManager = req("ScrollManager")
|
local ScrollManager = req("ScrollManager")
|
||||||
---@type ErrorHandler
|
|
||||||
local ErrorHandler = req("ErrorHandler")
|
|
||||||
---@type Element
|
---@type Element
|
||||||
local Element = req("Element")
|
local Element = req("Element")
|
||||||
|
|
||||||
-- externals
|
|
||||||
---@type Animation
|
|
||||||
local Animation = req("Animation")
|
|
||||||
local Transform = Animation.Transform
|
|
||||||
---@type Color
|
---@type Color
|
||||||
local Color = req("Color")
|
local Color = req("Color")
|
||||||
|
|
||||||
|
-- Optional modules (can be excluded in minimal builds)
|
||||||
|
local Blur = safeReq("Blur", true)
|
||||||
|
---@type Performance
|
||||||
|
local Performance = safeReq("Performance", true)
|
||||||
|
local ImageRenderer = safeReq("ImageRenderer", true)
|
||||||
|
local ImageScaler = safeReq("ImageScaler", true)
|
||||||
|
local NinePatch = safeReq("NinePatch", true)
|
||||||
|
local ImageCache = safeReq("ImageCache", true)
|
||||||
|
local GestureRecognizer = safeReq("GestureRecognizer", true)
|
||||||
|
---@type Animation
|
||||||
|
local Animation = safeReq("Animation", true)
|
||||||
---@type Theme
|
---@type Theme
|
||||||
local Theme = req("Theme")
|
local Theme = safeReq("Theme", true)
|
||||||
|
|
||||||
|
-- Handle Animation.Transform safely
|
||||||
|
local Transform = Animation.Transform or nil
|
||||||
|
|
||||||
local enums = utils.enums
|
local enums = utils.enums
|
||||||
|
|
||||||
---@class FlexLove
|
---@class FlexLove
|
||||||
@@ -103,35 +117,57 @@ function flexlove.init(config)
|
|||||||
enableRotation = config.errorLogRotateEnabled,
|
enableRotation = config.errorLogRotateEnabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
flexlove._Performance = Performance.init({
|
-- Initialize Performance if available
|
||||||
enabled = config.performanceMonitoring or true,
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.Performance") then
|
||||||
hudEnabled = false, -- Start with HUD disabled
|
flexlove._Performance = Performance.init({
|
||||||
hudToggleKey = config.performanceHudKey or "f3",
|
enabled = config.performanceMonitoring or true,
|
||||||
hudPosition = config.performanceHudPosition or { x = 10, y = 10 },
|
hudEnabled = false, -- Start with HUD disabled
|
||||||
warningThresholdMs = config.performanceWarningThreshold or 13.0,
|
hudToggleKey = config.performanceHudKey or "f3",
|
||||||
criticalThresholdMs = config.performanceCriticalThreshold or 16.67,
|
hudPosition = config.performanceHudPosition or { x = 10, y = 10 },
|
||||||
logToConsole = config.performanceLogToConsole or false,
|
warningThresholdMs = config.performanceWarningThreshold or 13.0,
|
||||||
logWarnings = config.performanceWarnings or false,
|
criticalThresholdMs = config.performanceCriticalThreshold or 16.67,
|
||||||
warningsEnabled = config.performanceWarnings or false,
|
logToConsole = config.performanceLogToConsole or false,
|
||||||
memoryProfiling = config.memoryProfiling or config.immediateMode and true or false,
|
logWarnings = config.performanceWarnings or false,
|
||||||
}, { ErrorHandler = flexlove._ErrorHandler })
|
warningsEnabled = config.performanceWarnings or false,
|
||||||
|
memoryProfiling = config.memoryProfiling or config.immediateMode and true or false,
|
||||||
|
}, { ErrorHandler = flexlove._ErrorHandler })
|
||||||
|
|
||||||
if config.immediateMode then
|
if config.immediateMode then
|
||||||
flexlove._Performance:registerTableForMonitoring("StateManager.stateStore", StateManager._getInternalState().stateStore)
|
flexlove._Performance:registerTableForMonitoring("StateManager.stateStore", StateManager._getInternalState().stateStore)
|
||||||
flexlove._Performance:registerTableForMonitoring("StateManager.stateMetadata", StateManager._getInternalState().stateMetadata)
|
flexlove._Performance:registerTableForMonitoring("StateManager.stateMetadata", StateManager._getInternalState().stateMetadata)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
flexlove._Performance = Performance
|
||||||
end
|
end
|
||||||
|
|
||||||
ImageRenderer.init({ ErrorHandler = flexlove._ErrorHandler, utils = flexlove._utils })
|
-- Initialize optional modules if available
|
||||||
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.ImageRenderer") then
|
||||||
|
ImageRenderer.init({ ErrorHandler = flexlove._ErrorHandler, utils = utils })
|
||||||
|
end
|
||||||
|
|
||||||
ImageScaler.init({ ErrorHandler = flexlove._ErrorHandler })
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.ImageScaler") then
|
||||||
|
ImageScaler.init({ ErrorHandler = flexlove._ErrorHandler })
|
||||||
|
end
|
||||||
|
|
||||||
NinePatch.init({ ErrorHandler = flexlove._ErrorHandler })
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.NinePatch") then
|
||||||
|
NinePatch.init({ ErrorHandler = flexlove._ErrorHandler })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialize required modules
|
||||||
Units.init({ Context = Context, ErrorHandler = flexlove._ErrorHandler })
|
Units.init({ Context = Context, ErrorHandler = flexlove._ErrorHandler })
|
||||||
Color.init({ ErrorHandler = flexlove._ErrorHandler })
|
Color.init({ ErrorHandler = flexlove._ErrorHandler })
|
||||||
utils.init({ ErrorHandler = flexlove._ErrorHandler })
|
utils.init({ ErrorHandler = flexlove._ErrorHandler })
|
||||||
Animation.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color })
|
|
||||||
Theme.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color, utils = utils })
|
-- Initialize optional Animation module
|
||||||
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.Animation") then
|
||||||
|
Animation.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialize optional Theme module
|
||||||
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.Theme") then
|
||||||
|
Theme.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color, utils = utils })
|
||||||
|
end
|
||||||
|
|
||||||
LayoutEngine.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance })
|
LayoutEngine.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance })
|
||||||
EventHandler.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance, InputEvent = InputEvent, utils = utils })
|
EventHandler.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance, InputEvent = InputEvent, utils = utils })
|
||||||
|
|
||||||
@@ -175,7 +211,7 @@ function flexlove.init(config)
|
|||||||
flexlove.scaleFactors.y = currentHeight / flexlove.baseScale.height
|
flexlove.scaleFactors.y = currentHeight / flexlove.baseScale.height
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.theme then
|
if config.theme and ModuleLoader.isModuleLoaded(modulePath .. "modules.Theme") then
|
||||||
local success, err = pcall(function()
|
local success, err = pcall(function()
|
||||||
if type(config.theme) == "string" then
|
if type(config.theme) == "string" then
|
||||||
Theme.load(config.theme)
|
Theme.load(config.theme)
|
||||||
@@ -268,7 +304,9 @@ function flexlove.resize()
|
|||||||
flexlove.scaleFactors.y = newHeight / flexlove.baseScale.height
|
flexlove.scaleFactors.y = newHeight / flexlove.baseScale.height
|
||||||
end
|
end
|
||||||
|
|
||||||
Blur.clearCache()
|
if ModuleLoader.isModuleLoaded(modulePath .. "modules.Blur") then
|
||||||
|
Blur.clearCache()
|
||||||
|
end
|
||||||
|
|
||||||
-- Release old canvases explicitly
|
-- Release old canvases explicitly
|
||||||
if flexlove._gameCanvas then
|
if flexlove._gameCanvas then
|
||||||
|
|||||||
190
modules/ModuleLoader.lua
Normal file
190
modules/ModuleLoader.lua
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
---@class ModuleLoader
|
||||||
|
local ModuleLoader = {}
|
||||||
|
|
||||||
|
-- Module registry to track loaded vs. stub modules
|
||||||
|
ModuleLoader._registry = {}
|
||||||
|
ModuleLoader._ErrorHandler = nil
|
||||||
|
|
||||||
|
--- Initialize ModuleLoader with dependencies
|
||||||
|
---@param deps table
|
||||||
|
function ModuleLoader.init(deps)
|
||||||
|
ModuleLoader._ErrorHandler = deps.ErrorHandler
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a null-object stub for a missing optional module
|
||||||
|
--- Provides safe defaults that won't cause runtime errors
|
||||||
|
---@param moduleName string
|
||||||
|
---@return table
|
||||||
|
local function createNullObject(moduleName)
|
||||||
|
local stub = {
|
||||||
|
_isStub = true,
|
||||||
|
_moduleName = moduleName,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Common method stubs that return safe defaults
|
||||||
|
local metatable = {
|
||||||
|
__index = function(_, key)
|
||||||
|
-- Common initialization method
|
||||||
|
if key == "init" then
|
||||||
|
return function() return stub end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common constructor method
|
||||||
|
if key == "new" then
|
||||||
|
return function() return stub end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common draw method
|
||||||
|
if key == "draw" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common update method
|
||||||
|
if key == "update" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common render method
|
||||||
|
if key == "render" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common cleanup method
|
||||||
|
if key == "destroy" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common cleanup method
|
||||||
|
if key == "cleanup" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common clear method
|
||||||
|
if key == "clear" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common reset method
|
||||||
|
if key == "reset" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common get method
|
||||||
|
if key == "get" then
|
||||||
|
return function() return nil end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common set method
|
||||||
|
if key == "set" then
|
||||||
|
return function() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common load method
|
||||||
|
if key == "load" then
|
||||||
|
return function() return stub end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Common cache-related methods
|
||||||
|
if key == "cache" or key == "getCache" or key == "clearCache" then
|
||||||
|
return function() return {} end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return nil for unknown properties (allows safe property access)
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Make function calls safe (in case the stub itself is called)
|
||||||
|
__call = function()
|
||||||
|
return stub
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(stub, metatable)
|
||||||
|
return stub
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Safely require a module with graceful fallback for optional modules
|
||||||
|
--- Returns the module if it exists, or a null-object stub if it's optional and missing
|
||||||
|
--- Throws an error if a required module is missing
|
||||||
|
---@param modulePath string Full path to the module (e.g., "modules.Performance")
|
||||||
|
---@param isOptional boolean If true, returns null-object on failure; if false, throws error
|
||||||
|
---@return table module The loaded module or a null-object stub
|
||||||
|
function ModuleLoader.safeRequire(modulePath, isOptional)
|
||||||
|
-- Check if already loaded
|
||||||
|
if ModuleLoader._registry[modulePath] then
|
||||||
|
return ModuleLoader._registry[modulePath]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Attempt to load the module
|
||||||
|
local success, result = pcall(require, modulePath)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
-- Module loaded successfully
|
||||||
|
ModuleLoader._registry[modulePath] = result
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
-- Module failed to load
|
||||||
|
if isOptional then
|
||||||
|
-- Create null-object stub for optional module
|
||||||
|
local stub = createNullObject(modulePath)
|
||||||
|
ModuleLoader._registry[modulePath] = stub
|
||||||
|
|
||||||
|
-- Log warning about missing optional module
|
||||||
|
if ModuleLoader._ErrorHandler then
|
||||||
|
ModuleLoader._ErrorHandler:warn(
|
||||||
|
"ModuleLoader",
|
||||||
|
string.format("Optional module '%s' not found, using stub implementation", modulePath)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return stub
|
||||||
|
else
|
||||||
|
-- Required module is missing - throw error
|
||||||
|
error(string.format("Required module '%s' not found: %s", modulePath, tostring(result)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if a module is actually loaded (not a stub)
|
||||||
|
---@param modulePath string Full path to the module
|
||||||
|
---@return boolean isLoaded True if module is loaded, false if it's a stub or not loaded
|
||||||
|
function ModuleLoader.isModuleLoaded(modulePath)
|
||||||
|
local module = ModuleLoader._registry[modulePath]
|
||||||
|
if not module then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if it's a stub
|
||||||
|
return not module._isStub
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get list of all loaded modules
|
||||||
|
---@return table modules List of module paths that are actually loaded (not stubs)
|
||||||
|
function ModuleLoader.getLoadedModules()
|
||||||
|
local loaded = {}
|
||||||
|
for path, module in pairs(ModuleLoader._registry) do
|
||||||
|
if not module._isStub then
|
||||||
|
table.insert(loaded, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get list of all stub modules
|
||||||
|
---@return table stubs List of module paths that are stubs
|
||||||
|
function ModuleLoader.getStubModules()
|
||||||
|
local stubs = {}
|
||||||
|
for path, module in pairs(ModuleLoader._registry) do
|
||||||
|
if module._isStub then
|
||||||
|
table.insert(stubs, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return stubs
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clear the module registry (useful for testing)
|
||||||
|
function ModuleLoader._clearRegistry()
|
||||||
|
ModuleLoader._registry = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return ModuleLoader
|
||||||
196
testing/__tests__/module_loader_test.lua
Normal file
196
testing/__tests__/module_loader_test.lua
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
local lu = require("testing.luaunit")
|
||||||
|
local loveStub = require("testing.loveStub")
|
||||||
|
|
||||||
|
-- Set up love stub globally
|
||||||
|
_G.love = loveStub
|
||||||
|
|
||||||
|
-- Load modules
|
||||||
|
local function req(name)
|
||||||
|
return require("modules." .. name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ErrorHandler = req("ErrorHandler")
|
||||||
|
local ModuleLoader = req("ModuleLoader")
|
||||||
|
|
||||||
|
-- Module path for testing
|
||||||
|
local modulePath = ""
|
||||||
|
|
||||||
|
TestModuleLoader = {}
|
||||||
|
|
||||||
|
function TestModuleLoader:setUp()
|
||||||
|
-- Initialize ErrorHandler
|
||||||
|
ErrorHandler.init({})
|
||||||
|
|
||||||
|
-- Initialize ModuleLoader
|
||||||
|
ModuleLoader.init({ ErrorHandler = ErrorHandler })
|
||||||
|
|
||||||
|
-- Clear registry before each test
|
||||||
|
ModuleLoader._clearRegistry()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:tearDown()
|
||||||
|
-- Clear registry after each test
|
||||||
|
ModuleLoader._clearRegistry()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_safeRequire_loads_existing_module()
|
||||||
|
-- Test loading an existing required module
|
||||||
|
local utils = ModuleLoader.safeRequire(modulePath .. "modules.utils", false)
|
||||||
|
|
||||||
|
lu.assertNotNil(utils)
|
||||||
|
lu.assertIsTable(utils)
|
||||||
|
lu.assertIsNil(utils._isStub)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_safeRequire_returns_stub_for_missing_optional_module()
|
||||||
|
-- Test loading a non-existent optional module
|
||||||
|
local fakeModule = ModuleLoader.safeRequire(modulePath .. "modules.NonExistentModule", true)
|
||||||
|
|
||||||
|
lu.assertNotNil(fakeModule)
|
||||||
|
lu.assertIsTable(fakeModule)
|
||||||
|
lu.assertTrue(fakeModule._isStub)
|
||||||
|
lu.assertEquals(fakeModule._moduleName, modulePath .. "modules.NonExistentModule")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_safeRequire_throws_error_for_missing_required_module()
|
||||||
|
-- Test loading a non-existent required module should throw error
|
||||||
|
lu.assertErrorMsgContains(
|
||||||
|
"Required module",
|
||||||
|
function()
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.NonExistentModule", false)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_has_safe_init_method()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsFunction(stub.init)
|
||||||
|
local result = stub.init()
|
||||||
|
lu.assertEquals(result, stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_has_safe_new_method()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsFunction(stub.new)
|
||||||
|
local result = stub.new()
|
||||||
|
lu.assertEquals(result, stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_has_safe_draw_method()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsFunction(stub.draw)
|
||||||
|
-- Should not throw error
|
||||||
|
stub.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_has_safe_update_method()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsFunction(stub.update)
|
||||||
|
-- Should not throw error
|
||||||
|
stub.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_has_safe_clear_method()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsFunction(stub.clear)
|
||||||
|
-- Should not throw error
|
||||||
|
stub.clear()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_has_safe_clearCache_method()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsFunction(stub.clearCache)
|
||||||
|
local result = stub.clearCache()
|
||||||
|
lu.assertIsTable(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_returns_nil_for_unknown_properties()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertIsNil(stub.unknownProperty)
|
||||||
|
lu.assertIsNil(stub.anotherUnknownProperty)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_stub_callable_returns_itself()
|
||||||
|
local stub = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
local result = stub()
|
||||||
|
lu.assertEquals(result, stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_isModuleLoaded_returns_true_for_loaded_module()
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.utils", false)
|
||||||
|
|
||||||
|
lu.assertTrue(ModuleLoader.isModuleLoaded(modulePath .. "modules.utils"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_isModuleLoaded_returns_false_for_stub_module()
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
lu.assertFalse(ModuleLoader.isModuleLoaded(modulePath .. "modules.FakeModule"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_isModuleLoaded_returns_false_for_unloaded_module()
|
||||||
|
lu.assertFalse(ModuleLoader.isModuleLoaded(modulePath .. "modules.NeverLoaded"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_getLoadedModules_returns_only_real_modules()
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.utils", false)
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.FakeModule1", true)
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.FakeModule2", true)
|
||||||
|
|
||||||
|
local loaded = ModuleLoader.getLoadedModules()
|
||||||
|
|
||||||
|
lu.assertIsTable(loaded)
|
||||||
|
-- Should only contain utils (real module)
|
||||||
|
local hasUtils = false
|
||||||
|
for _, path in ipairs(loaded) do
|
||||||
|
if path == modulePath .. "modules.utils" then
|
||||||
|
hasUtils = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lu.assertTrue(hasUtils)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_getStubModules_returns_only_stubs()
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.utils", false)
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.FakeModule1", true)
|
||||||
|
ModuleLoader.safeRequire(modulePath .. "modules.FakeModule2", true)
|
||||||
|
|
||||||
|
local stubs = ModuleLoader.getStubModules()
|
||||||
|
|
||||||
|
lu.assertIsTable(stubs)
|
||||||
|
-- Should contain 2 stubs
|
||||||
|
lu.assertEquals(#stubs, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_safeRequire_caches_modules()
|
||||||
|
-- Load module twice
|
||||||
|
local module1 = ModuleLoader.safeRequire(modulePath .. "modules.utils", false)
|
||||||
|
local module2 = ModuleLoader.safeRequire(modulePath .. "modules.utils", false)
|
||||||
|
|
||||||
|
-- Should return same instance
|
||||||
|
lu.assertEquals(module1, module2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestModuleLoader:test_safeRequire_caches_stubs()
|
||||||
|
-- Load stub twice
|
||||||
|
local stub1 = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
local stub2 = ModuleLoader.safeRequire(modulePath .. "modules.FakeModule", true)
|
||||||
|
|
||||||
|
-- Should return same instance
|
||||||
|
lu.assertEquals(stub1, stub2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run tests if executed directly
|
||||||
|
if not _G.RUNNING_ALL_TESTS then
|
||||||
|
os.exit(lu.LuaUnit.run())
|
||||||
|
end
|
||||||
|
|
||||||
|
return TestModuleLoader
|
||||||
@@ -46,6 +46,7 @@ local testFiles = {
|
|||||||
"testing/__tests__/image_scaler_test.lua",
|
"testing/__tests__/image_scaler_test.lua",
|
||||||
"testing/__tests__/input_event_test.lua",
|
"testing/__tests__/input_event_test.lua",
|
||||||
"testing/__tests__/layout_engine_test.lua",
|
"testing/__tests__/layout_engine_test.lua",
|
||||||
|
"testing/__tests__/module_loader_test.lua",
|
||||||
"testing/__tests__/ninepatch_test.lua",
|
"testing/__tests__/ninepatch_test.lua",
|
||||||
"testing/__tests__/performance_test.lua",
|
"testing/__tests__/performance_test.lua",
|
||||||
"testing/__tests__/renderer_test.lua",
|
"testing/__tests__/renderer_test.lua",
|
||||||
|
|||||||
Reference in New Issue
Block a user