calc module

This commit is contained in:
Michael Freno
2025-12-07 00:56:55 -05:00
parent f532837cf3
commit 502eeb1e11
23 changed files with 870 additions and 68 deletions

View File

@@ -29,7 +29,7 @@ jobs:
VERSION=$(grep -m 1 "_VERSION" FlexLove.lua | awk -F'"' '{print $2}') VERSION=$(grep -m 1 "_VERSION" FlexLove.lua | awk -F'"' '{print $2}')
echo "Triggered manually, using FlexLove.lua version" echo "Triggered manually, using FlexLove.lua version"
fi fi
# Verify version was extracted # Verify version was extracted
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
echo "ERROR: Failed to extract version from FlexLove.lua" echo "ERROR: Failed to extract version from FlexLove.lua"
@@ -37,7 +37,7 @@ jobs:
grep "_VERSION" FlexLove.lua || echo "No _VERSION found" grep "_VERSION" FlexLove.lua || echo "No _VERSION found"
exit 1 exit 1
fi fi
echo "Extracted version: $VERSION" echo "Extracted version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
@@ -128,7 +128,7 @@ jobs:
echo "Contents:" echo "Contents:"
ls -la ls -la
echo "" echo ""
# Create all 4 profile packages # Create all 4 profile packages
./scripts/create-profile-packages.sh ./scripts/create-profile-packages.sh
@@ -180,10 +180,10 @@ jobs:
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
CURRENT_TAG="v${VERSION}" CURRENT_TAG="v${VERSION}"
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
# Get the previous tag (exclude current tag if it exists) # Get the previous tag (exclude current tag if it exists)
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${CURRENT_TAG}$" | head -1) PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${CURRENT_TAG}$" | head -1)
# Generate changelog # Generate changelog
if [ -n "$PREVIOUS_TAG" ]; then if [ -n "$PREVIOUS_TAG" ]; then
echo "## Changes since $PREVIOUS_TAG" > release_notes.md echo "## Changes since $PREVIOUS_TAG" > release_notes.md
@@ -201,7 +201,7 @@ jobs:
echo "" >> release_notes.md echo "" >> release_notes.md
echo "" >> release_notes.md echo "" >> release_notes.md
fi fi
# Append the rest of the release notes # Append the rest of the release notes
cat >> release_notes.md << 'EOF' cat >> release_notes.md << 'EOF'
## Build Profiles ## Build Profiles
@@ -212,7 +212,7 @@ jobs:
|---------|------|-------------|---------| |---------|------|-------------|---------|
| **Minimal** | ~70% | Core functionality only | `flexlove-minimal-v${{ steps.version.outputs.version }}.zip` | | **Minimal** | ~70% | Core functionality only | `flexlove-minimal-v${{ steps.version.outputs.version }}.zip` |
| **Slim** | ~80% | + Animation and Image support | `flexlove-slim-v${{ steps.version.outputs.version }}.zip` | | **Slim** | ~80% | + Animation and Image support | `flexlove-slim-v${{ steps.version.outputs.version }}.zip` |
| **Default** | ~95% | + Theme and Blur effects | `flexlove-default-v${{ steps.version.outputs.version }}.zip` | | **Default** | ~95% | + Theme, Calc, Blur effects | `flexlove-default-v${{ steps.version.outputs.version }}.zip` |
| **Full** | 100% | All modules including debugging tools | `flexlove-full-v${{ steps.version.outputs.version }}.zip` | | **Full** | 100% | All modules including debugging tools | `flexlove-full-v${{ steps.version.outputs.version }}.zip` |
**Choose the profile that matches your needs!** See the [Build Profiles Documentation](https://github.com/${{ github.repository }}/blob/main/docs/BUILD_PROFILES.md) for detailed module listings. **Choose the profile that matches your needs!** See the [Build Profiles Documentation](https://github.com/${{ github.repository }}/blob/main/docs/BUILD_PROFILES.md) for detailed module listings.

View File

@@ -20,6 +20,7 @@ end
-- Required core modules -- Required core modules
local utils = req("utils") local utils = req("utils")
local Calc = req("Calc")
local Units = req("Units") local Units = req("Units")
local Context = req("Context") local Context = req("Context")
---@type StateManager ---@type StateManager
@@ -176,7 +177,8 @@ function flexlove.init(config)
end end
-- Initialize required modules -- Initialize required modules
Units.init({ Context = Context, ErrorHandler = flexlove._ErrorHandler }) Calc.init({ ErrorHandler = flexlove._ErrorHandler })
Units.init({ Context = Context, ErrorHandler = flexlove._ErrorHandler, Calc = Calc })
Color.init({ ErrorHandler = flexlove._ErrorHandler, FFI = flexlove._FFI }) Color.init({ ErrorHandler = flexlove._ErrorHandler, FFI = flexlove._FFI })
utils.init({ ErrorHandler = flexlove._ErrorHandler }) utils.init({ ErrorHandler = flexlove._ErrorHandler })
@@ -197,6 +199,7 @@ function flexlove.init(config)
Context = Context, Context = Context,
Theme = Theme, Theme = Theme,
Color = Color, Color = Color,
Calc = Calc,
Units = Units, Units = Units,
Blur = Blur, Blur = Blur,
ImageRenderer = ImageRenderer, ImageRenderer = ImageRenderer,
@@ -1103,6 +1106,21 @@ function flexlove.getStateStats()
return StateManager.getStats() return StateManager.getStats()
end end
--- Create a calc() expression for dynamic CSS-like calculations
--- Use this to create responsive layouts that adapt to viewport and parent dimensions
--- @usage
--- local button = FlexLove.new({
--- x = FlexLove.calc("50% - 10vw"),
--- y = FlexLove.calc("50% - 5vh"),
--- width = "20vw",
--- height = "10vh",
--- })
---@param expr string The calc expression (e.g., "50% - 10vw", "100px + 20%")
---@return table calcObject A calc expression object that will be evaluated during layout
function flexlove.calc(expr)
return Calc.new(expr)
end
flexlove.Animation = Animation flexlove.Animation = Animation
flexlove.Color = Color flexlove.Color = Color
flexlove.Theme = Theme flexlove.Theme = Theme

View File

@@ -110,25 +110,6 @@ FlexLöve supports optional modules to reduce bundle size for different use case
- **Default (~95%)** - Adds themes, blur effects, and gestures - **Default (~95%)** - Adds themes, blur effects, and gestures
- **Full (100%)** - Everything including performance monitoring - **Full (100%)** - Everything including performance monitoring
### Example: Minimal Build
For a lightweight build, exclude these optional module files:
```
modules/Animation.lua
modules/Theme.lua
modules/Blur.lua
modules/ImageRenderer.lua
modules/ImageScaler.lua
modules/ImageCache.lua
modules/NinePatch.lua
modules/GestureRecognizer.lua
modules/Performance.lua
```
The library automatically detects missing modules and provides safe no-op stubs. No code changes needed!
📖 **See [BUILD_PROFILES.md](./docs/BUILD_PROFILES.md) and [MODULE_DEPENDENCIES.md](./docs/MODULE_DEPENDENCIES.md) for detailed information.**
## Documentation ## Documentation
📚 **[View Full API Documentation](https://mikefreno.github.io/FlexLove/api.html)** 📚 **[View Full API Documentation](https://mikefreno.github.io/FlexLove/api.html)**
@@ -574,6 +555,39 @@ local element = FlexLove.new({
}) })
``` ```
#### Dynamic Calculations with calc()
Use `calc()` for CSS-like dynamic calculations in layout properties:
```lua
-- Center a button horizontally (accounting for its width)
local button = FlexLove.new({
x = FlexLove.calc("50% - 10vw"), -- Centers a 20vw wide button
y = "50vh",
width = "20vw",
height = "10vh",
text = "Centered Button"
})
-- Complex calculations with multiple operations
local sidebar = FlexLove.new({
width = FlexLove.calc("100vw - 300px"), -- Full width minus fixed sidebar
height = FlexLove.calc("100vh - 50px"), -- Full height minus header
x = "300px",
y = "50px"
})
-- Using parentheses for order of operations
local panel = FlexLove.new({
width = FlexLove.calc("(100vw - 40px) / 3"), -- Three equal columns with 40px total padding
padding = { left = "10px", right = "10px" }
})
```
**Supported operations:** `+`, `-`, `*`, `/`
**Supported units:** `px`, `%`, `vw`, `vh`, `ew`, `eh`
**Note:** Element width/height units (`ew`, `eh`) cannot be used in position calculations (`x`, `y`) due to circular dependencies.
### Animations ### Animations
Create smooth transitions: Create smooth transitions:

View File

@@ -174,10 +174,8 @@ Each profile package includes:
|---------|---------|------------------| |---------|---------|------------------|
| **Minimal** | 19 core modules | ~70% of full | | **Minimal** | 19 core modules | ~70% of full |
| **Slim** | 24 modules | ~80% of full | | **Slim** | 24 modules | ~80% of full |
| **Default** | 27 modules + themes | ~95% of full | | **Default** | 28 modules + theme examples | ~95% of full |
| **Full** | 29 modules + themes | 100% | | **Full** | 30 modules + theme examples | 100% |
**Note:** All profiles now include UTF8.lua for Lua 5.1+ compatibility.
Users who want examples, documentation source, or development tools should clone the full repository. Users who want examples, documentation source, or development tools should clone the full repository.

404
modules/Calc.lua Normal file
View File

@@ -0,0 +1,404 @@
--- Utility module for parsing and evaluating CSS-like calc() expressions
--- Supports arithmetic operations (+, -, *, /) with mixed units (px, %, vw, vh, ew, eh)
---@class Calc
local Calc = {}
--- Initialize Calc module with dependencies
---@param deps table Dependencies: { ErrorHandler = table? }
function Calc.init(deps)
Calc._ErrorHandler = deps.ErrorHandler
end
--- Token types for lexical analysis
local TokenType = {
NUMBER = "NUMBER",
UNIT = "UNIT",
PLUS = "PLUS",
MINUS = "MINUS",
MULTIPLY = "MULTIPLY",
DIVIDE = "DIVIDE",
LPAREN = "LPAREN",
RPAREN = "RPAREN",
EOF = "EOF",
}
--- Tokenize a calc expression string into tokens
---@param expr string The expression to tokenize (e.g., "50% - 10vw")
---@return table|nil tokens Array of tokens with type, value, unit
---@return string? error Error message if tokenization fails
local function tokenize(expr)
local tokens = {}
local i = 1
local len = #expr
while i <= len do
local char = expr:sub(i, i)
-- Skip whitespace
if char:match("%s") then
i = i + 1
-- Number (including decimals, but NOT negative - handled separately below)
elseif char:match("%d") or (char == "." and expr:sub(i + 1, i + 1):match("%d")) then
local numStr = ""
-- Parse integer and decimal parts
while i <= len and (expr:sub(i, i):match("%d") or expr:sub(i, i) == ".") do
numStr = numStr .. expr:sub(i, i)
i = i + 1
end
local num = tonumber(numStr)
if not num then
return nil, "Invalid number: " .. numStr
end
-- Check for unit following the number
local unitStr = ""
while i <= len and expr:sub(i, i):match("[%a%%]") do
unitStr = unitStr .. expr:sub(i, i)
i = i + 1
end
-- Default to px if no unit
if unitStr == "" then
unitStr = "px"
end
-- Validate unit
local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
if not validUnits[unitStr] then
return nil, "Invalid unit: " .. unitStr
end
table.insert(tokens, {
type = TokenType.NUMBER,
value = num,
unit = unitStr,
})
-- Operators
elseif char == "+" then
table.insert(tokens, { type = TokenType.PLUS })
i = i + 1
elseif char == "-" then
-- Check if this is a negative number or subtraction
-- It's a negative number if previous token is an operator or opening paren
local prevToken = tokens[#tokens]
if not prevToken or prevToken.type == TokenType.PLUS or prevToken.type == TokenType.MINUS
or prevToken.type == TokenType.MULTIPLY or prevToken.type == TokenType.DIVIDE
or prevToken.type == TokenType.LPAREN then
-- This is a negative number, continue to number parsing
local numStr = "-"
i = i + 1
-- Parse integer and decimal parts
while i <= len and (expr:sub(i, i):match("%d") or expr:sub(i, i) == ".") do
numStr = numStr .. expr:sub(i, i)
i = i + 1
end
local num = tonumber(numStr)
if not num then
return nil, "Invalid number: " .. numStr
end
-- Check for unit following the number
local unitStr = ""
while i <= len and expr:sub(i, i):match("[%a%%]") do
unitStr = unitStr .. expr:sub(i, i)
i = i + 1
end
-- Default to px if no unit
if unitStr == "" then
unitStr = "px"
end
-- Validate unit
local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
if not validUnits[unitStr] then
return nil, "Invalid unit: " .. unitStr
end
table.insert(tokens, {
type = TokenType.NUMBER,
value = num,
unit = unitStr,
})
else
-- This is subtraction operator
table.insert(tokens, { type = TokenType.MINUS })
i = i + 1
end
elseif char == "*" then
table.insert(tokens, { type = TokenType.MULTIPLY })
i = i + 1
elseif char == "/" then
table.insert(tokens, { type = TokenType.DIVIDE })
i = i + 1
elseif char == "(" then
table.insert(tokens, { type = TokenType.LPAREN })
i = i + 1
elseif char == ")" then
table.insert(tokens, { type = TokenType.RPAREN })
i = i + 1
else
return nil, "Unexpected character: " .. char
end
end
table.insert(tokens, { type = TokenType.EOF })
return tokens
end
--- Parser for calc expressions using recursive descent
---@class Parser
---@field tokens table Array of tokens
---@field pos number Current token position
local Parser = {}
Parser.__index = Parser
--- Create a new parser
---@param tokens table Array of tokens
---@return Parser
function Parser.new(tokens)
local self = setmetatable({}, Parser)
self.tokens = tokens
self.pos = 1
return self
end
--- Get current token
---@return table token Current token
function Parser:current()
return self.tokens[self.pos]
end
--- Advance to next token
function Parser:advance()
self.pos = self.pos + 1
end
--- Parse expression (handles + and -)
---@return table ast Abstract syntax tree node
function Parser:parseExpression()
local left = self:parseTerm()
while self:current().type == TokenType.PLUS or self:current().type == TokenType.MINUS do
local op = self:current().type
self:advance()
local right = self:parseTerm()
left = {
type = op == TokenType.PLUS and "add" or "subtract",
left = left,
right = right,
}
end
return left
end
--- Parse term (handles * and /)
---@return table ast Abstract syntax tree node
function Parser:parseTerm()
local left = self:parseFactor()
while self:current().type == TokenType.MULTIPLY or self:current().type == TokenType.DIVIDE do
local op = self:current().type
self:advance()
local right = self:parseFactor()
left = {
type = op == TokenType.MULTIPLY and "multiply" or "divide",
left = left,
right = right,
}
end
return left
end
--- Parse factor (handles numbers and parentheses)
---@return table ast Abstract syntax tree node
function Parser:parseFactor()
local token = self:current()
if token.type == TokenType.NUMBER then
self:advance()
return {
type = "number",
value = token.value,
unit = token.unit,
}
elseif token.type == TokenType.LPAREN then
self:advance()
local expr = self:parseExpression()
if self:current().type ~= TokenType.RPAREN then
error("Expected closing parenthesis")
end
self:advance()
return expr
else
error("Unexpected token: " .. token.type)
end
end
--- Parse the tokens into an AST
---@return table ast Abstract syntax tree
function Parser:parse()
local ast = self:parseExpression()
if self:current().type ~= TokenType.EOF then
error("Unexpected tokens after expression")
end
return ast
end
--- Create a calc expression object that can be resolved later
--- This is the main API function that users call
---@param expr string The calc expression (e.g., "50% - 10vw")
---@return table calcObject A calc expression object with AST
function Calc.new(expr)
-- Tokenize
local tokens, err = tokenize(expr)
if not tokens then
if Calc._ErrorHandler then
Calc._ErrorHandler:warn("Calc", "VAL_006", {
expression = expr,
error = err,
})
end
-- Return a fallback calc object that resolves to 0
return {
_isCalc = true,
_expr = expr,
_ast = nil,
_error = err,
}
end
-- Parse
local parser = Parser.new(tokens)
local success, ast = pcall(function()
return parser:parse()
end)
if not success then
if Calc._ErrorHandler then
Calc._ErrorHandler:warn("Calc", "VAL_006", {
expression = expr,
error = ast, -- ast contains error message on failure
})
end
-- Return a fallback calc object that resolves to 0
return {
_isCalc = true,
_expr = expr,
_ast = nil,
_error = ast,
}
end
return {
_isCalc = true,
_expr = expr,
_ast = ast,
}
end
--- Check if a value is a calc expression
---@param value any The value to check
---@return boolean isCalc True if value is a calc expression
function Calc.isCalc(value)
return type(value) == "table" and value._isCalc == true
end
--- Resolve a calc expression to pixel value
---@param calcObj table The calc expression object
---@param viewportWidth number Viewport width in pixels
---@param viewportHeight number Viewport height in pixels
---@param parentSize number? Parent dimension for percentage units
---@param elementWidth number? Element width for ew units
---@param elementHeight number? Element height for eh units
---@return number resolvedValue Resolved pixel value
function Calc.resolve(calcObj, viewportWidth, viewportHeight, parentSize, elementWidth, elementHeight)
if not calcObj._ast then
-- Error during parsing, return 0
return 0
end
--- Evaluate AST node recursively
---@param node table AST node
---@return number value Evaluated value in pixels
local function evaluate(node)
if node.type == "number" then
-- Convert unit to pixels
local value = node.value
local unit = node.unit
if unit == "px" then
return value
elseif unit == "%" then
if not parentSize then
if Calc._ErrorHandler then
Calc._ErrorHandler:warn("Calc", "LAY_003", {
unit = "%",
issue = "parent dimension not available",
})
end
return 0
end
return (value / 100) * parentSize
elseif unit == "vw" then
return (value / 100) * viewportWidth
elseif unit == "vh" then
return (value / 100) * viewportHeight
elseif unit == "ew" then
if not elementWidth then
if Calc._ErrorHandler then
Calc._ErrorHandler:warn("Calc", "LAY_003", {
unit = "ew",
issue = "element width not available",
})
end
return 0
end
return (value / 100) * elementWidth
elseif unit == "eh" then
if not elementHeight then
if Calc._ErrorHandler then
Calc._ErrorHandler:warn("Calc", "LAY_003", {
unit = "eh",
issue = "element height not available",
})
end
return 0
end
return (value / 100) * elementHeight
else
return 0
end
elseif node.type == "add" then
return evaluate(node.left) + evaluate(node.right)
elseif node.type == "subtract" then
return evaluate(node.left) - evaluate(node.right)
elseif node.type == "multiply" then
return evaluate(node.left) * evaluate(node.right)
elseif node.type == "divide" then
local divisor = evaluate(node.right)
if divisor == 0 then
if Calc._ErrorHandler then
Calc._ErrorHandler:warn("Calc", "VAL_006", {
expression = calcObj._expr,
error = "Division by zero",
})
end
return 0
end
return evaluate(node.left) / divisor
else
return 0
end
end
return evaluate(calcObj._ast)
end
return Calc

View File

@@ -1092,7 +1092,7 @@ function Element.new(props)
-- Handle x position with units -- Handle x position with units
if props.x then if props.x then
if type(props.x) == "string" then if type(props.x) == "string" or type(props.x) == "table" then
local value, unit = Element._Units.parse(props.x) local value, unit = Element._Units.parse(props.x)
self.units.x = { value = value, unit = unit } self.units.x = { value = value, unit = unit }
self.x = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth) self.x = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
@@ -1108,7 +1108,7 @@ function Element.new(props)
-- Handle y position with units -- Handle y position with units
if props.y then if props.y then
if type(props.y) == "string" then if type(props.y) == "string" or type(props.y) == "table" then
local value, unit = Element._Units.parse(props.y) local value, unit = Element._Units.parse(props.y)
self.units.y = { value = value, unit = unit } self.units.y = { value = value, unit = unit }
self.y = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight) self.y = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
@@ -1181,7 +1181,7 @@ function Element.new(props)
-- Handle x position with units -- Handle x position with units
if props.x then if props.x then
if type(props.x) == "string" then if type(props.x) == "string" or type(props.x) == "table" then
local value, unit = Element._Units.parse(props.x) local value, unit = Element._Units.parse(props.x)
self.units.x = { value = value, unit = unit } self.units.x = { value = value, unit = unit }
local parentWidth = self.parent.width local parentWidth = self.parent.width
@@ -1200,7 +1200,7 @@ function Element.new(props)
-- Handle y position with units -- Handle y position with units
if props.y then if props.y then
if type(props.y) == "string" then if type(props.y) == "string" or type(props.y) == "table" then
local value, unit = Element._Units.parse(props.y) local value, unit = Element._Units.parse(props.y)
self.units.y = { value = value, unit = unit } self.units.y = { value = value, unit = unit }
local parentHeight = self.parent.height local parentHeight = self.parent.height
@@ -1225,7 +1225,7 @@ function Element.new(props)
local baseY = self.parent.y + self.parent.padding.top local baseY = self.parent.y + self.parent.padding.top
if props.x then if props.x then
if type(props.x) == "string" then if type(props.x) == "string" or type(props.x) == "table" then
local value, unit = Element._Units.parse(props.x) local value, unit = Element._Units.parse(props.x)
self.units.x = { value = value, unit = unit } self.units.x = { value = value, unit = unit }
local parentWidth = self.parent.width local parentWidth = self.parent.width
@@ -1243,7 +1243,7 @@ function Element.new(props)
end end
if props.y then if props.y then
if type(props.y) == "string" then if type(props.y) == "string" or type(props.y) == "table" then
local value, unit = Element._Units.parse(props.y) local value, unit = Element._Units.parse(props.y)
self.units.y = { value = value, unit = unit } self.units.y = { value = value, unit = unit }
parentHeight = self.parent.height parentHeight = self.parent.height

View File

@@ -46,8 +46,8 @@ local ErrorCodes = {
VAL_006 = { VAL_006 = {
code = "FLEXLOVE_VAL_006", code = "FLEXLOVE_VAL_006",
category = "VAL", category = "VAL",
description = "Invalid file path", description = "Invalid calc() expression or calculation error",
suggestion = "Check that the file path is correct and the file exists", suggestion = "Check calc() syntax and ensure no division by zero. Format: calc('value1 operator value2') with operators: +, -, *, / and units: px, %, vw, vh, ew, eh",
}, },
VAL_007 = { VAL_007 = {
code = "FLEXLOVE_VAL_007", code = "FLEXLOVE_VAL_007",

View File

@@ -3,29 +3,36 @@
---@class Units ---@class Units
---@field _Context table? Context module dependency ---@field _Context table? Context module dependency
---@field _ErrorHandler table? ErrorHandler module dependency ---@field _ErrorHandler table? ErrorHandler module dependency
---@field _Calc table? Calc module dependency
local Units = {} local Units = {}
--- Initialize Units module with dependencies --- Initialize Units module with dependencies
---@param deps table Dependencies: { Context = table?, ErrorHandler = table? } ---@param deps table Dependencies: { Context = table?, ErrorHandler = table?, Calc = table? }
function Units.init(deps) function Units.init(deps)
Units._Context = deps.Context Units._Context = deps.Context
Units._ErrorHandler = deps.ErrorHandler Units._ErrorHandler = deps.ErrorHandler
Units._Calc = deps.Calc
end end
--- Parse a unit value into numeric value and unit type --- Parse a unit value into numeric value and unit type
--- Supports: px (pixels), % (percentage), vw/vh (viewport), ew/eh (element) --- Supports: px (pixels), % (percentage), vw/vh (viewport), ew/eh (element), and calc() expressions
---@param value string|number The value to parse (e.g., "50px", "10%", "2vw", 100) ---@param value string|number|table The value to parse (e.g., "50px", "10%", "2vw", 100, or calc object)
---@return number numericValue The numeric portion of the value ---@return number|table numericValue The numeric portion of the value or calc object
---@return string unitType The unit type ("px", "%", "vw", "vh", "ew", "eh") ---@return string unitType The unit type ("px", "%", "vw", "vh", "ew", "eh", "calc")
function Units.parse(value) function Units.parse(value)
-- Check if value is a calc expression
if Units._Calc and Units._Calc.isCalc(value) then
return value, "calc"
end
if type(value) == "number" then if type(value) == "number" then
return value, "px" return value, "px"
end end
if type(value) ~= "string" then if type(value) ~= "string" and type(value) ~= "table" then
Units._ErrorHandler:warn("Units", "VAL_001", { Units._ErrorHandler:warn("Units", "VAL_001", {
property = "unit value", property = "unit value",
expected = "string or number", expected = "string, number, or calc object",
got = type(value), got = type(value),
}) })
return 0, "px" return 0, "px"
@@ -87,15 +94,28 @@ function Units.parse(value)
end end
--- Convert relative units to absolute pixel values --- Convert relative units to absolute pixel values
--- Resolves %, vw, vh units based on viewport and parent dimensions --- Resolves %, vw, vh units based on viewport and parent dimensions, and evaluates calc() expressions
---@param value number Numeric value to convert ---@param value number|table Numeric value to convert or calc object
---@param unit string Unit type ("px", "%", "vw", "vh", "ew", "eh") ---@param unit string Unit type ("px", "%", "vw", "vh", "ew", "eh", "calc")
---@param viewportWidth number Current viewport width in pixels ---@param viewportWidth number Current viewport width in pixels
---@param viewportHeight number Current viewport height in pixels ---@param viewportHeight number Current viewport height in pixels
---@param parentSize number? Required for percentage units (parent dimension in pixels) ---@param parentSize number? Required for percentage units (parent dimension in pixels)
---@param elementWidth number? Required for ew units in calc expressions (element width in pixels)
---@param elementHeight number? Required for eh units in calc expressions (element height in pixels)
---@return number resolvedValue Resolved pixel value ---@return number resolvedValue Resolved pixel value
function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize) function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize, elementWidth, elementHeight)
if unit == "px" then if unit == "calc" then
-- Resolve calc expression
if Units._Calc then
return Units._Calc.resolve(value, viewportWidth, viewportHeight, parentSize, elementWidth, elementHeight)
else
Units._ErrorHandler:warn("Units", "VAL_006", {
unit = "calc",
issue = "Calc module not available",
})
return 0
end
elseif unit == "px" then
return value return value
elseif unit == "%" then elseif unit == "%" then
if not parentSize then if not parentSize then
@@ -113,7 +133,7 @@ function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
else else
Units._ErrorHandler:warn("Units", "VAL_005", { Units._ErrorHandler:warn("Units", "VAL_005", {
unit = unit, unit = unit,
validUnits = "px, %, vw, vh, ew, eh", validUnits = "px, %, vw, vh, ew, eh, calc",
}) })
return 0 return 0
end end
@@ -169,26 +189,26 @@ function Units.resolveSpacing(spacingProps, parentWidth, parentHeight)
local horizontal = spacingProps.horizontal local horizontal = spacingProps.horizontal
if vertical then if vertical then
if type(vertical) == "string" then if type(vertical) == "string" or (Units._Calc and Units._Calc.isCalc(vertical)) then
local value, unit = Units.parse(vertical) local value, unit = Units.parse(vertical)
vertical = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight) vertical = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight, nil, nil)
end end
end end
if horizontal then if horizontal then
if type(horizontal) == "string" then if type(horizontal) == "string" or (Units._Calc and Units._Calc.isCalc(horizontal)) then
local value, unit = Units.parse(horizontal) local value, unit = Units.parse(horizontal)
horizontal = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth) horizontal = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth, nil, nil)
end end
end end
for _, side in ipairs({ "top", "right", "bottom", "left" }) do for _, side in ipairs({ "top", "right", "bottom", "left" }) do
local value = spacingProps[side] local value = spacingProps[side]
if value then if value then
if type(value) == "string" then if type(value) == "string" or (Units._Calc and Units._Calc.isCalc(value)) then
local numValue, unit = Units.parse(value) local numValue, unit = Units.parse(value)
local parentSize = (side == "top" or side == "bottom") and parentHeight or parentWidth local parentSize = (side == "top" or side == "bottom") and parentHeight or parentWidth
result[side] = Units.resolve(numValue, unit, viewportWidth, viewportHeight, parentSize) result[side] = Units.resolve(numValue, unit, viewportWidth, viewportHeight, parentSize, nil, nil)
else else
result[side] = value result[side] = value
end end
@@ -205,10 +225,15 @@ function Units.resolveSpacing(spacingProps, parentWidth, parentHeight)
end end
--- Validate a unit string format --- Validate a unit string format
--- Checks if the string can be successfully parsed as a valid unit --- Checks if the string can be successfully parsed as a valid unit or calc expression
---@param unitStr string The unit string to validate (e.g., "50px", "10%") ---@param unitStr string|table The unit string to validate (e.g., "50px", "10%") or calc object
---@return boolean isValid True if the unit string is valid, false otherwise ---@return boolean isValid True if the unit string is valid, false otherwise
function Units.isValid(unitStr) function Units.isValid(unitStr)
-- Check if it's a calc expression
if Units._Calc and Units._Calc.isCalc(unitStr) then
return true
end
if type(unitStr) ~= "string" then if type(unitStr) ~= "string" then
return false return false
end end

View File

@@ -61,10 +61,10 @@ get_modules() {
echo "utils.lua Units.lua Context.lua StateManager.lua ErrorHandler.lua Color.lua InputEvent.lua TextEditor.lua LayoutEngine.lua Renderer.lua EventHandler.lua ScrollManager.lua Element.lua RoundedRect.lua Grid.lua ModuleLoader.lua types.lua FFI.lua UTF8.lua Animation.lua NinePatch.lua ImageRenderer.lua ImageScaler.lua ImageCache.lua" echo "utils.lua Units.lua Context.lua StateManager.lua ErrorHandler.lua Color.lua InputEvent.lua TextEditor.lua LayoutEngine.lua Renderer.lua EventHandler.lua ScrollManager.lua Element.lua RoundedRect.lua Grid.lua ModuleLoader.lua types.lua FFI.lua UTF8.lua Animation.lua NinePatch.lua ImageRenderer.lua ImageScaler.lua ImageCache.lua"
;; ;;
default) default)
echo "utils.lua Units.lua Context.lua StateManager.lua ErrorHandler.lua Color.lua InputEvent.lua TextEditor.lua LayoutEngine.lua Renderer.lua EventHandler.lua ScrollManager.lua Element.lua RoundedRect.lua Grid.lua ModuleLoader.lua types.lua FFI.lua UTF8.lua Animation.lua NinePatch.lua ImageRenderer.lua ImageScaler.lua ImageCache.lua Theme.lua Blur.lua GestureRecognizer.lua" echo "utils.lua Units.lua Calc.lua Context.lua StateManager.lua ErrorHandler.lua Color.lua InputEvent.lua TextEditor.lua LayoutEngine.lua Renderer.lua EventHandler.lua ScrollManager.lua Element.lua RoundedRect.lua Grid.lua ModuleLoader.lua types.lua FFI.lua UTF8.lua Animation.lua NinePatch.lua ImageRenderer.lua ImageScaler.lua ImageCache.lua Theme.lua Blur.lua GestureRecognizer.lua"
;; ;;
full) full)
echo "utils.lua Units.lua Context.lua StateManager.lua ErrorHandler.lua Color.lua InputEvent.lua TextEditor.lua LayoutEngine.lua Renderer.lua EventHandler.lua ScrollManager.lua Element.lua RoundedRect.lua Grid.lua ModuleLoader.lua types.lua FFI.lua UTF8.lua Animation.lua NinePatch.lua ImageRenderer.lua ImageScaler.lua ImageCache.lua Theme.lua Blur.lua GestureRecognizer.lua Performance.lua MemoryScanner.lua" echo "utils.lua Units.lua Calc.lua Context.lua StateManager.lua ErrorHandler.lua Color.lua InputEvent.lua TextEditor.lua LayoutEngine.lua Renderer.lua EventHandler.lua ScrollManager.lua Element.lua RoundedRect.lua Grid.lua ModuleLoader.lua types.lua FFI.lua UTF8.lua Animation.lua NinePatch.lua ImageRenderer.lua ImageScaler.lua ImageCache.lua Theme.lua Blur.lua GestureRecognizer.lua Performance.lua MemoryScanner.lua"
;; ;;
esac esac
} }

View File

@@ -11,6 +11,15 @@ local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler -- Initialize ErrorHandler
ErrorHandler.init({}) ErrorHandler.init({})
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
-- Load FlexLove which properly initializes all dependencies -- Load FlexLove which properly initializes all dependencies
local FlexLove = require("FlexLove") local FlexLove = require("FlexLove")

View File

@@ -0,0 +1,232 @@
---@diagnostic disable: undefined-global, undefined-field
local lu = require("testing.luaunit")
-- Mock love globals for testing environment
_G.love = _G.love or {}
_G.love.graphics = _G.love.graphics or {}
_G.love.graphics.getDimensions = function()
return 1920, 1080
end
_G.love.window = _G.love.window or {}
_G.love.window.getMode = function()
return 1920, 1080
end
-- Load Calc module directly
local Calc = require("modules.Calc")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize with error handler
ErrorHandler.init({})
Calc.init({ ErrorHandler = ErrorHandler })
---@class TestCalc
TestCalc = {}
function TestCalc:setUp()
-- Fresh initialization for each test
end
function TestCalc:tearDown()
-- Cleanup after each test
end
--- Test basic arithmetic: addition
function TestCalc:testBasicAddition()
local calcObj = Calc.new("100px + 50px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 150)
end
--- Test basic arithmetic: subtraction
function TestCalc:testBasicSubtraction()
local calcObj = Calc.new("100px - 30px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 70)
end
--- Test basic arithmetic: multiplication
function TestCalc:testBasicMultiplication()
local calcObj = Calc.new("10px * 5")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 50)
end
--- Test basic arithmetic: division
function TestCalc:testBasicDivision()
local calcObj = Calc.new("100px / 4")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 25)
end
--- Test negative numbers
function TestCalc:testNegativeNumbers()
local calcObj = Calc.new("-50px + 100px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 50)
end
--- Test decimal numbers
function TestCalc:testDecimalNumbers()
local calcObj = Calc.new("10.5px + 5.5px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 16)
end
--- Test percentage units
function TestCalc:testPercentageUnits()
local calcObj = Calc.new("50% + 25%")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, 1000, nil, nil) -- parent size = 1000
lu.assertEquals(result, 750) -- 50% of 1000 + 25% of 1000 = 500 + 250
end
--- Test viewport width units (vw)
function TestCalc:testViewportWidthUnits()
local calcObj = Calc.new("50vw - 10vw")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 768) -- 40% of 1920 = 768
end
--- Test viewport height units (vh)
function TestCalc:testViewportHeightUnits()
local calcObj = Calc.new("50vh + 10vh")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 648) -- 60% of 1080 = 648
end
--- Test mixed units
function TestCalc:testMixedUnits()
local calcObj = Calc.new("50% - 10vw")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, 1000, nil, nil)
lu.assertEquals(result, 308) -- 50% of 1000 - 10% of 1920 = 500 - 192 = 308
end
--- Test complex expression with multiple operations
function TestCalc:testComplexExpression()
local calcObj = Calc.new("100px + 50px - 20px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 130)
end
--- Test parentheses for precedence
function TestCalc:testParentheses()
local calcObj = Calc.new("(100px + 50px) * 2")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 300)
end
--- Test nested parentheses
function TestCalc:testNestedParentheses()
local calcObj = Calc.new("((100px + 50px) / 3) * 2")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 100) -- (150 / 3) * 2 = 50 * 2 = 100
end
--- Test operator precedence (multiplication before addition)
function TestCalc:testOperatorPrecedence()
local calcObj = Calc.new("100px + 50px * 2")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 200) -- 100 + (50 * 2) = 100 + 100 = 200
end
--- Test centering use case (50% - 10vw)
function TestCalc:testCenteringUseCase()
local calcObj = Calc.new("50% - 10vw")
lu.assertTrue(Calc.isCalc(calcObj))
-- Assuming element width is 20vw (384px) and parent width is 1920px
-- 50% of parent - 10vw should center it
local result = Calc.resolve(calcObj, 1920, 1080, 1920, nil, nil)
lu.assertEquals(result, 768) -- 50% of 1920 - 10% of 1920 = 960 - 192 = 768
end
--- Test element width units (ew)
function TestCalc:testElementWidthUnits()
local calcObj = Calc.new("100ew - 20ew")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, 500, nil) -- element width = 500
lu.assertEquals(result, 400) -- 80% of 500 = 400
end
--- Test element height units (eh)
function TestCalc:testElementHeightUnits()
local calcObj = Calc.new("50eh + 25eh")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, 300) -- element height = 300
lu.assertEquals(result, 225) -- 75% of 300 = 225
end
--- Test whitespace handling
function TestCalc:testWhitespaceHandling()
local calcObj = Calc.new(" 100px + 50px ")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 150)
end
--- Test zero value
function TestCalc:testZeroValue()
local calcObj = Calc.new("100px - 100px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 0)
end
--- Test single value (no operation)
function TestCalc:testSingleValue()
local calcObj = Calc.new("100px")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 100)
end
--- Test isCalc function with non-calc values
function TestCalc:testIsCalcWithNonCalcValues()
lu.assertFalse(Calc.isCalc("100px"))
lu.assertFalse(Calc.isCalc(100))
lu.assertFalse(Calc.isCalc(nil))
lu.assertFalse(Calc.isCalc({}))
end
--- Test division by zero error handling
function TestCalc:testDivisionByZeroHandling()
local calcObj = Calc.new("100px / 0")
lu.assertTrue(Calc.isCalc(calcObj))
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 0) -- Should return 0 on division by zero error
end
--- Test invalid expression error handling
function TestCalc:testInvalidExpressionHandling()
local calcObj = Calc.new("100px +") -- Incomplete expression
lu.assertTrue(Calc.isCalc(calcObj))
-- Should return 0 for invalid expressions
local result = Calc.resolve(calcObj, 1920, 1080, nil, nil, nil)
lu.assertEquals(result, 0)
end
--- Test complex real-world centering scenario
function TestCalc:testRealWorldCentering()
-- Button with 20vw width, centered at 50% - 10vw
local xCalc = Calc.new("50% - 10vw")
local result = Calc.resolve(xCalc, 1920, 1080, 1920, nil, nil)
-- Expected: 50% of 1920 - 10% of 1920 = 960 - 192 = 768
lu.assertEquals(result, 768)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(lu.LuaUnit.run())
end

View File

@@ -5,6 +5,16 @@
-- 3. Unsafe input access (nil dereference, division by zero, etc.) -- 3. Unsafe input access (nil dereference, division by zero, etc.)
package.path = package.path .. ";./?.lua;./modules/?.lua" package.path = package.path .. ";./?.lua;./modules/?.lua"
-- Add custom package searcher to handle FlexLove.modules.X imports
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
require("testing.loveStub") require("testing.loveStub")
local luaunit = require("testing.luaunit") local luaunit = require("testing.luaunit")
local FlexLove = require("FlexLove") local FlexLove = require("FlexLove")

View File

@@ -12,6 +12,15 @@ local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler -- Initialize ErrorHandler
ErrorHandler.init({}) ErrorHandler.init({})
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
-- Load FlexLove which properly initializes all dependencies -- Load FlexLove which properly initializes all dependencies
local FlexLove = require("FlexLove") local FlexLove = require("FlexLove")
local Element = require("modules.Element") local Element = require("modules.Element")

View File

@@ -1,3 +1,12 @@
-- Add custom package searcher to handle FlexLove.modules.X imports
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
local luaunit = require("testing.luaunit") local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler") local ErrorHandler = require("modules.ErrorHandler")
require("testing.loveStub") require("testing.loveStub")

View File

@@ -4,6 +4,16 @@ package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub") require("testing.loveStub")
local luaunit = require("testing.luaunit") local luaunit = require("testing.luaunit")
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
local FlexLove = require("FlexLove") local FlexLove = require("FlexLove")
TestGridLayout = {} TestGridLayout = {}

View File

@@ -518,6 +518,13 @@ function TestImageRendererElementIntegration:setUp()
local Renderer = require("modules.Renderer") local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler") local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache") local ImageCache = require("modules.ImageCache")
local Context = require("modules.Context")
local StateManager = require("modules.StateManager")
local InputEvent = require("modules.InputEvent")
local Theme = require("modules.Theme")
local TextEditor = require("modules.TextEditor")
local ScrollManager = require("modules.ScrollManager")
local RoundedRect = require("modules.RoundedRect")
self.deps = { self.deps = {
utils = utils, utils = utils,
@@ -529,7 +536,18 @@ function TestImageRendererElementIntegration:setUp()
ImageCache = ImageCache, ImageCache = ImageCache,
ImageRenderer = ImageRenderer, ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler, ErrorHandler = ErrorHandler,
Context = Context,
StateManager = StateManager,
InputEvent = InputEvent,
Theme = Theme,
TextEditor = TextEditor,
ScrollManager = ScrollManager,
RoundedRect = RoundedRect,
} }
-- Initialize Element with dependencies
Element.init(self.deps)
self.Element = Element self.Element = Element
end end

View File

@@ -6,10 +6,9 @@ ErrorHandler.init({})
require("testing.loveStub") require("testing.loveStub")
local ImageScaler = require("modules.ImageScaler") local ImageScaler = require("modules.ImageScaler")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler -- Initialize ImageScaler with ErrorHandler
ErrorHandler.init({}) ImageScaler.init({ ErrorHandler = ErrorHandler })
TestImageScaler = {} TestImageScaler = {}

View File

@@ -11,9 +11,19 @@ local luaunit = require("testing.luaunit")
local LayoutEngine = require("modules.LayoutEngine") local LayoutEngine = require("modules.LayoutEngine")
local Units = require("modules.Units") local Units = require("modules.Units")
local utils = require("modules.utils") local utils = require("modules.utils")
local FlexLove = require("FlexLove")
local ErrorHandler = require("modules.ErrorHandler") local ErrorHandler = require("modules.ErrorHandler")
local Animation = require("modules.Animation") local Animation = require("modules.Animation")
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
local FlexLove = require("FlexLove")
local Transform = Animation.Transform local Transform = Animation.Transform
-- ============================================================================ -- ============================================================================

View File

@@ -7,6 +7,15 @@ local loveStub = require("testing.loveStub")
-- Set up stub before requiring modules -- Set up stub before requiring modules
_G.love = loveStub _G.love = loveStub
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
local FlexLove = require("FlexLove") local FlexLove = require("FlexLove")
local Performance = require("modules.Performance") local Performance = require("modules.Performance")
local Element = require('modules.Element') local Element = require('modules.Element')

View File

@@ -11,6 +11,16 @@ local Theme = require("modules.Theme")
local Blur = require("modules.Blur") local Blur = require("modules.Blur")
local utils = require("modules.utils") local utils = require("modules.utils")
local ErrorHandler = require("modules.ErrorHandler") local ErrorHandler = require("modules.ErrorHandler")
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
local FlexLove = require("FlexLove") local FlexLove = require("FlexLove")
-- Initialize ErrorHandler -- Initialize ErrorHandler

View File

@@ -1,5 +1,14 @@
package.path = package.path .. ";./?.lua;./modules/?.lua" package.path = package.path .. ";./?.lua;./modules/?.lua"
-- Add custom package searcher to handle FlexLove.modules.X imports
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
require("testing.loveStub") require("testing.loveStub")
local lu = require("testing.luaunit") local lu = require("testing.luaunit")

View File

@@ -9,9 +9,17 @@ require("testing.loveStub")
local luaunit = require("testing.luaunit") local luaunit = require("testing.luaunit")
local Units = require("modules.Units") local Units = require("modules.Units")
local Context = require("modules.Context") local Context = require("modules.Context")
local ErrorHandler = require("modules.ErrorHandler")
local Calc = require("modules.Calc")
-- Initialize Units module with Context -- Initialize ErrorHandler
Units.init({ Context = Context }) ErrorHandler.init({})
-- Initialize Calc
Calc.init({ ErrorHandler = ErrorHandler })
-- Initialize Units module with dependencies
Units.init({ Context = Context, ErrorHandler = ErrorHandler, Calc = Calc })
-- Mock viewport dimensions for consistent tests -- Mock viewport dimensions for consistent tests
local MOCK_VIEWPORT_WIDTH = 1920 local MOCK_VIEWPORT_WIDTH = 1920

View File

@@ -56,6 +56,7 @@ local testFiles = {
"testing/__tests__/theme_test.lua", "testing/__tests__/theme_test.lua",
"testing/__tests__/units_test.lua", "testing/__tests__/units_test.lua",
"testing/__tests__/utils_test.lua", "testing/__tests__/utils_test.lua",
"testing/__tests__/calc_test.lua",
-- Feature/Integration tests -- Feature/Integration tests
"testing/__tests__/critical_failures_test.lua", "testing/__tests__/critical_failures_test.lua",
"testing/__tests__/flexlove_test.lua", "testing/__tests__/flexlove_test.lua",