element di migration

This commit is contained in:
Michael Freno
2025-11-14 22:41:24 -05:00
parent 48d44a1a11
commit f35bb11770
4 changed files with 525 additions and 242 deletions

View File

@@ -10,6 +10,17 @@ local Units = req("Units")
local Context = req("Context") local Context = req("Context")
local StateManager = req("StateManager") local StateManager = req("StateManager")
local ErrorHandler = req("ErrorHandler") local ErrorHandler = req("ErrorHandler")
local ImageRenderer = req("ImageRenderer")
local NinePatch = req("NinePatch")
local RoundedRect = req("RoundedRect")
local ImageCache = req("ImageCache")
local Grid = req("Grid")
local InputEvent = req("InputEvent")
local TextEditor = req("TextEditor")
local LayoutEngine = req("LayoutEngine")
local Renderer = req("Renderer")
local EventHandler = req("EventHandler")
local ScrollManager = req("ScrollManager")
---@type Element ---@type Element
local Element = req("Element") local Element = req("Element")
@@ -22,6 +33,28 @@ local Color = req("Color")
local Theme = req("Theme") local Theme = req("Theme")
local enums = utils.enums local enums = utils.enums
Element.defaultDependencies = {
Context = Context,
Theme = Theme,
Color = Color,
Units = Units,
Blur = Blur,
ImageRenderer = ImageRenderer,
NinePatch = NinePatch,
RoundedRect = RoundedRect,
ImageCache = ImageCache,
utils = utils,
Grid = Grid,
InputEvent = InputEvent,
StateManager = StateManager,
TextEditor = TextEditor,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ScrollManager = ScrollManager,
ErrorHandler = ErrorHandler,
}
---@class FlexLove ---@class FlexLove
local flexlove = Context local flexlove = Context
@@ -630,7 +663,7 @@ function flexlove.new(props)
-- If not in immediate mode, use standard Element.new -- If not in immediate mode, use standard Element.new
if not flexlove._immediateMode then if not flexlove._immediateMode then
return Element.new(props) return Element.new(props, Element.defaultDependencies)
end end
-- Auto-begin frame if not manually started (convenience feature) -- Auto-begin frame if not manually started (convenience feature)
@@ -656,7 +689,7 @@ function flexlove.new(props)
props._scrollY = state._scrollY or 0 props._scrollY = state._scrollY or 0
-- Create the element -- Create the element
local element = Element.new(props) local element = Element.new(props, Element.defaultDependencies)
-- Bind persistent state to element (ImmediateModeState) -- Bind persistent state to element (ImmediateModeState)
-- Restore event handler state -- Restore event handler state

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env lua
-- Script to add dependency injection to Element.lua
local function read_file(path)
local f = io.open(path, "r")
if not f then
error("Could not open file: " .. path)
end
local content = f:read("*all")
f:close()
return content
end
local function write_file(path, content)
local f = io.open(path, "w")
if not f then
error("Could not write file: " .. path)
end
f:write(content)
f:close()
end
local element_path = "modules/Element.lua"
print("Reading " .. element_path)
local content = read_file(element_path)
-- Step 1: Add defaultDependencies after Element table definition
print("Step 1: Adding default dependencies...")
local element_def = "local Element = {}\nElement.__index = Element\n"
local new_element_def = [[local Element = {}
Element.__index = Element
-- Default dependencies (can be overridden for testing)
Element.defaultDependencies = {
Context = Context,
Theme = Theme,
Color = Color,
Units = Units,
Blur = Blur,
ImageRenderer = ImageRenderer,
NinePatch = NinePatch,
RoundedRect = RoundedRect,
ImageCache = ImageCache,
utils = utils,
Grid = Grid,
InputEvent = InputEvent,
StateManager = StateManager,
TextEditor = TextEditor,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ScrollManager = ScrollManager,
ErrorHandler = ErrorHandler,
}
]]
if not content:find("Element.defaultDependencies") then
content = content:gsub(element_def:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), new_element_def)
print(" ✓ Added defaultDependencies")
else
print(" - Already has defaultDependencies")
end
-- Step 2: Update Element.new signature
print("Step 2: Updating Element.new signature...")
local old_signature = "function Element.new%(props%)\n local self = setmetatable%({}, Element%)"
local new_signature = [[function Element.new(props, deps)
local self = setmetatable({}, Element)
-- Initialize dependencies (allow injection for testing)
self._deps = deps or Element.defaultDependencies]]
if not content:find("self._deps") then
content = content:gsub(old_signature, new_signature)
print(" ✓ Updated signature and added deps initialization")
else
print(" - Already has deps initialization")
end
-- Step 3: Update comment for Element.new
print("Step 3: Updating function documentation...")
content = content:gsub(
"%-%-%-@param props ElementProps\n%-%-%-@return Element",
"---@param props ElementProps\n---@param deps table? Optional dependency injection (defaults to Element.defaultDependencies)\n---@return Element"
)
print("Writing changes to " .. element_path)
write_file(element_path, content)
print("✓ Done!")
print("\nNext steps:")
print("1. Run tests to ensure nothing broke")
print("2. Gradually replace module references with self._deps.ModuleName")
print("3. Create mock dependencies for testing")

View File

@@ -0,0 +1,186 @@
-- Test that demonstrates dependency injection with mocked dependencies
package.path = package.path .. ";../../?.lua;../?.lua"
local luaunit = require("testing.luaunit")
local Element = require("modules.Element")
-- Mock dependencies
local function createMockDeps()
return {
Context = {
_immediateMode = false,
defaultTheme = "test",
scaleFactors = { x = 1, y = 1 },
registerElement = function() end,
topElements = {},
},
Theme = {
Manager = {
new = function()
return {
getThemeState = function() return "normal" end,
update = function() end,
}
end
},
},
Color = {
new = function(r, g, b, a)
return { r = r or 0, g = g or 0, b = b or 0, a = a or 1 }
end,
},
Units = {
getViewport = function() return 1920, 1080 end,
parse = function(value)
if type(value) == "number" then
return value, "px"
end
return 100, "px"
end,
},
Blur = {},
ImageRenderer = {},
NinePatch = {},
RoundedRect = {},
ImageCache = {},
utils = {
enums = {
Positioning = { RELATIVE = "relative", ABSOLUTE = "absolute", FLEX = "flex", GRID = "grid" },
FlexDirection = { HORIZONTAL = "horizontal", VERTICAL = "vertical" },
JustifyContent = { FLEX_START = "flex-start", FLEX_END = "flex-end", CENTER = "center" },
AlignContent = { STRETCH = "stretch", FLEX_START = "flex-start", FLEX_END = "flex-end" },
AlignItems = { STRETCH = "stretch", FLEX_START = "flex-start", FLEX_END = "flex-end", CENTER = "center" },
TextAlign = { LEFT = "left", CENTER = "center", RIGHT = "right" },
AlignSelf = { AUTO = "auto", STRETCH = "stretch", FLEX_START = "flex-start" },
JustifySelf = { AUTO = "auto", FLEX_START = "flex-start", FLEX_END = "flex-end" },
FlexWrap = { NOWRAP = "nowrap", WRAP = "wrap" },
},
validateEnum = function() end,
validateRange = function() end,
validateType = function() end,
resolveTextSizePreset = function(size) return size end,
getModifiers = function() return false, false, false, false end,
},
Grid = {},
InputEvent = {
new = function() return {} end,
},
StateManager = {
generateID = function() return "test-id" end,
},
TextEditor = {
new = function()
return {
initialize = function() end,
}
end,
},
LayoutEngine = {
new = function()
return {
initialize = function() end,
calculateLayout = function() end,
}
end,
},
Renderer = {
new = function()
return {
initialize = function() end,
draw = function() end,
}
end,
},
EventHandler = {
new = function()
return {
initialize = function() end,
getState = function() return {} end,
}
end,
},
ScrollManager = {
new = function()
return {
initialize = function() end,
}
end,
},
ErrorHandler = {
handle = function() end,
},
}
end
TestDependencyInjection = {}
function TestDependencyInjection:test_element_with_mocked_dependencies()
-- Create mock dependencies
local mockDeps = createMockDeps()
-- Track if Context.registerElement was called
local registerCalled = false
mockDeps.Context.registerElement = function()
registerCalled = true
end
-- Create element with mocked dependencies
local element = Element.new({
id = "test-element",
width = 100,
height = 100,
x = 0,
y = 0,
}, mockDeps)
-- Verify element was created
luaunit.assertNotNil(element)
luaunit.assertEquals(element.id, "test-element")
luaunit.assertEquals(element.width, 100)
luaunit.assertEquals(element.height, 100)
-- Verify the element is using our mocked dependencies
luaunit.assertEquals(element._deps, mockDeps)
-- Verify Context.registerElement was called
luaunit.assertTrue(registerCalled)
end
function TestDependencyInjection:test_element_without_deps_should_error()
-- Element.new now requires deps parameter
local success, err = pcall(function()
Element.new({
id = "test-element",
width = 100,
height = 100,
})
end)
luaunit.assertFalse(success)
luaunit.assertNotNil(err)
luaunit.assertStrContains(err, "deps")
end
function TestDependencyInjection:test_can_mock_specific_module_behavior()
local mockDeps = createMockDeps()
-- Mock Units.parse to return specific values
local parseCallCount = 0
mockDeps.Units.parse = function(value)
parseCallCount = parseCallCount + 1
return 200, "px" -- Always return 200px
end
-- Create element (this will call Units.parse)
local element = Element.new({
id = "test",
width = "50%", -- This should be parsed by our mock
height = 100,
x = 0,
y = 0,
}, mockDeps)
-- Verify our mock was called
luaunit.assertTrue(parseCallCount > 0, "Units.parse should have been called")
end
os.exit(luaunit.LuaUnit.run())