calc module
This commit is contained in:
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
VERSION=$(grep -m 1 "_VERSION" FlexLove.lua | awk -F'"' '{print $2}')
|
||||
echo "Triggered manually, using FlexLove.lua version"
|
||||
fi
|
||||
|
||||
|
||||
# Verify version was extracted
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "ERROR: Failed to extract version from FlexLove.lua"
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
grep "_VERSION" FlexLove.lua || echo "No _VERSION found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "Extracted version: $VERSION"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
echo "Contents:"
|
||||
ls -la
|
||||
echo ""
|
||||
|
||||
|
||||
# Create all 4 profile packages
|
||||
./scripts/create-profile-packages.sh
|
||||
|
||||
@@ -180,10 +180,10 @@ jobs:
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
CURRENT_TAG="v${VERSION}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
|
||||
# Get the previous tag (exclude current tag if it exists)
|
||||
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${CURRENT_TAG}$" | head -1)
|
||||
|
||||
|
||||
# Generate changelog
|
||||
if [ -n "$PREVIOUS_TAG" ]; then
|
||||
echo "## Changes since $PREVIOUS_TAG" > release_notes.md
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
echo "" >> release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
fi
|
||||
|
||||
|
||||
# Append the rest of the release notes
|
||||
cat >> release_notes.md << 'EOF'
|
||||
## Build Profiles
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
|---------|------|-------------|---------|
|
||||
| **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` |
|
||||
| **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` |
|
||||
|
||||
**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.
|
||||
|
||||
20
FlexLove.lua
20
FlexLove.lua
@@ -20,6 +20,7 @@ end
|
||||
|
||||
-- Required core modules
|
||||
local utils = req("utils")
|
||||
local Calc = req("Calc")
|
||||
local Units = req("Units")
|
||||
local Context = req("Context")
|
||||
---@type StateManager
|
||||
@@ -176,7 +177,8 @@ function flexlove.init(config)
|
||||
end
|
||||
|
||||
-- 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 })
|
||||
utils.init({ ErrorHandler = flexlove._ErrorHandler })
|
||||
|
||||
@@ -197,6 +199,7 @@ function flexlove.init(config)
|
||||
Context = Context,
|
||||
Theme = Theme,
|
||||
Color = Color,
|
||||
Calc = Calc,
|
||||
Units = Units,
|
||||
Blur = Blur,
|
||||
ImageRenderer = ImageRenderer,
|
||||
@@ -1103,6 +1106,21 @@ function flexlove.getStateStats()
|
||||
return StateManager.getStats()
|
||||
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.Color = Color
|
||||
flexlove.Theme = Theme
|
||||
|
||||
52
README.md
52
README.md
@@ -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
|
||||
- **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
|
||||
|
||||
📚 **[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
|
||||
|
||||
Create smooth transitions:
|
||||
|
||||
@@ -174,10 +174,8 @@ Each profile package includes:
|
||||
|---------|---------|------------------|
|
||||
| **Minimal** | 19 core modules | ~70% of full |
|
||||
| **Slim** | 24 modules | ~80% of full |
|
||||
| **Default** | 27 modules + themes | ~95% of full |
|
||||
| **Full** | 29 modules + themes | 100% |
|
||||
|
||||
**Note:** All profiles now include UTF8.lua for Lua 5.1+ compatibility.
|
||||
| **Default** | 28 modules + theme examples | ~95% of full |
|
||||
| **Full** | 30 modules + theme examples | 100% |
|
||||
|
||||
Users who want examples, documentation source, or development tools should clone the full repository.
|
||||
|
||||
|
||||
404
modules/Calc.lua
Normal file
404
modules/Calc.lua
Normal 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
|
||||
@@ -1092,7 +1092,7 @@ function Element.new(props)
|
||||
|
||||
-- Handle x position with units
|
||||
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)
|
||||
self.units.x = { value = value, unit = unit }
|
||||
self.x = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
||||
@@ -1108,7 +1108,7 @@ function Element.new(props)
|
||||
|
||||
-- Handle y position with units
|
||||
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)
|
||||
self.units.y = { value = value, unit = unit }
|
||||
self.y = Element._Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
||||
@@ -1181,7 +1181,7 @@ function Element.new(props)
|
||||
|
||||
-- Handle x position with units
|
||||
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)
|
||||
self.units.x = { value = value, unit = unit }
|
||||
local parentWidth = self.parent.width
|
||||
@@ -1200,7 +1200,7 @@ function Element.new(props)
|
||||
|
||||
-- Handle y position with units
|
||||
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)
|
||||
self.units.y = { value = value, unit = unit }
|
||||
local parentHeight = self.parent.height
|
||||
@@ -1225,7 +1225,7 @@ function Element.new(props)
|
||||
local baseY = self.parent.y + self.parent.padding.top
|
||||
|
||||
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)
|
||||
self.units.x = { value = value, unit = unit }
|
||||
local parentWidth = self.parent.width
|
||||
@@ -1243,7 +1243,7 @@ function Element.new(props)
|
||||
end
|
||||
|
||||
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)
|
||||
self.units.y = { value = value, unit = unit }
|
||||
parentHeight = self.parent.height
|
||||
|
||||
@@ -46,8 +46,8 @@ local ErrorCodes = {
|
||||
VAL_006 = {
|
||||
code = "FLEXLOVE_VAL_006",
|
||||
category = "VAL",
|
||||
description = "Invalid file path",
|
||||
suggestion = "Check that the file path is correct and the file exists",
|
||||
description = "Invalid calc() expression or calculation error",
|
||||
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 = {
|
||||
code = "FLEXLOVE_VAL_007",
|
||||
|
||||
@@ -3,29 +3,36 @@
|
||||
---@class Units
|
||||
---@field _Context table? Context module dependency
|
||||
---@field _ErrorHandler table? ErrorHandler module dependency
|
||||
---@field _Calc table? Calc module dependency
|
||||
local Units = {}
|
||||
|
||||
--- 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)
|
||||
Units._Context = deps.Context
|
||||
Units._ErrorHandler = deps.ErrorHandler
|
||||
Units._Calc = deps.Calc
|
||||
end
|
||||
|
||||
--- Parse a unit value into numeric value and unit type
|
||||
--- Supports: px (pixels), % (percentage), vw/vh (viewport), ew/eh (element)
|
||||
---@param value string|number The value to parse (e.g., "50px", "10%", "2vw", 100)
|
||||
---@return number numericValue The numeric portion of the value
|
||||
---@return string unitType The unit type ("px", "%", "vw", "vh", "ew", "eh")
|
||||
--- Supports: px (pixels), % (percentage), vw/vh (viewport), ew/eh (element), and calc() expressions
|
||||
---@param value string|number|table The value to parse (e.g., "50px", "10%", "2vw", 100, or calc object)
|
||||
---@return number|table numericValue The numeric portion of the value or calc object
|
||||
---@return string unitType The unit type ("px", "%", "vw", "vh", "ew", "eh", "calc")
|
||||
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
|
||||
return value, "px"
|
||||
end
|
||||
|
||||
if type(value) ~= "string" then
|
||||
if type(value) ~= "string" and type(value) ~= "table" then
|
||||
Units._ErrorHandler:warn("Units", "VAL_001", {
|
||||
property = "unit value",
|
||||
expected = "string or number",
|
||||
expected = "string, number, or calc object",
|
||||
got = type(value),
|
||||
})
|
||||
return 0, "px"
|
||||
@@ -87,15 +94,28 @@ function Units.parse(value)
|
||||
end
|
||||
|
||||
--- Convert relative units to absolute pixel values
|
||||
--- Resolves %, vw, vh units based on viewport and parent dimensions
|
||||
---@param value number Numeric value to convert
|
||||
---@param unit string Unit type ("px", "%", "vw", "vh", "ew", "eh")
|
||||
--- Resolves %, vw, vh units based on viewport and parent dimensions, and evaluates calc() expressions
|
||||
---@param value number|table Numeric value to convert or calc object
|
||||
---@param unit string Unit type ("px", "%", "vw", "vh", "ew", "eh", "calc")
|
||||
---@param viewportWidth number Current viewport width in pixels
|
||||
---@param viewportHeight number Current viewport height 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
|
||||
function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
|
||||
if unit == "px" then
|
||||
function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize, elementWidth, elementHeight)
|
||||
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
|
||||
elseif unit == "%" then
|
||||
if not parentSize then
|
||||
@@ -113,7 +133,7 @@ function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
|
||||
else
|
||||
Units._ErrorHandler:warn("Units", "VAL_005", {
|
||||
unit = unit,
|
||||
validUnits = "px, %, vw, vh, ew, eh",
|
||||
validUnits = "px, %, vw, vh, ew, eh, calc",
|
||||
})
|
||||
return 0
|
||||
end
|
||||
@@ -169,26 +189,26 @@ function Units.resolveSpacing(spacingProps, parentWidth, parentHeight)
|
||||
local horizontal = spacingProps.horizontal
|
||||
|
||||
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)
|
||||
vertical = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
||||
vertical = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight, nil, nil)
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
horizontal = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
|
||||
horizontal = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth, nil, nil)
|
||||
end
|
||||
end
|
||||
|
||||
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
||||
local value = spacingProps[side]
|
||||
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 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
|
||||
result[side] = value
|
||||
end
|
||||
@@ -205,10 +225,15 @@ function Units.resolveSpacing(spacingProps, parentWidth, parentHeight)
|
||||
end
|
||||
|
||||
--- Validate a unit string format
|
||||
--- Checks if the string can be successfully parsed as a valid unit
|
||||
---@param unitStr string The unit string to validate (e.g., "50px", "10%")
|
||||
--- Checks if the string can be successfully parsed as a valid unit or calc expression
|
||||
---@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
|
||||
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
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -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"
|
||||
;;
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@ local ErrorHandler = require("modules.ErrorHandler")
|
||||
-- Initialize ErrorHandler
|
||||
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
|
||||
local FlexLove = require("FlexLove")
|
||||
|
||||
|
||||
232
testing/__tests__/calc_test.lua
Normal file
232
testing/__tests__/calc_test.lua
Normal 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
|
||||
@@ -5,6 +5,16 @@
|
||||
-- 3. Unsafe input access (nil dereference, division by zero, etc.)
|
||||
|
||||
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")
|
||||
local luaunit = require("testing.luaunit")
|
||||
local FlexLove = require("FlexLove")
|
||||
|
||||
@@ -12,6 +12,15 @@ local ErrorHandler = require("modules.ErrorHandler")
|
||||
-- Initialize ErrorHandler
|
||||
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
|
||||
local FlexLove = require("FlexLove")
|
||||
local Element = require("modules.Element")
|
||||
|
||||
@@ -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 ErrorHandler = require("modules.ErrorHandler")
|
||||
require("testing.loveStub")
|
||||
|
||||
@@ -4,6 +4,16 @@ package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||
|
||||
require("testing.loveStub")
|
||||
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")
|
||||
|
||||
TestGridLayout = {}
|
||||
|
||||
@@ -518,6 +518,13 @@ function TestImageRendererElementIntegration:setUp()
|
||||
local Renderer = require("modules.Renderer")
|
||||
local EventHandler = require("modules.EventHandler")
|
||||
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 = {
|
||||
utils = utils,
|
||||
@@ -529,7 +536,18 @@ function TestImageRendererElementIntegration:setUp()
|
||||
ImageCache = ImageCache,
|
||||
ImageRenderer = ImageRenderer,
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@ ErrorHandler.init({})
|
||||
require("testing.loveStub")
|
||||
|
||||
local ImageScaler = require("modules.ImageScaler")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
|
||||
-- Initialize ErrorHandler
|
||||
ErrorHandler.init({})
|
||||
-- Initialize ImageScaler with ErrorHandler
|
||||
ImageScaler.init({ ErrorHandler = ErrorHandler })
|
||||
|
||||
TestImageScaler = {}
|
||||
|
||||
|
||||
@@ -11,9 +11,19 @@ local luaunit = require("testing.luaunit")
|
||||
local LayoutEngine = require("modules.LayoutEngine")
|
||||
local Units = require("modules.Units")
|
||||
local utils = require("modules.utils")
|
||||
local FlexLove = require("FlexLove")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
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
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
@@ -7,6 +7,15 @@ local loveStub = require("testing.loveStub")
|
||||
-- Set up stub before requiring modules
|
||||
_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 Performance = require("modules.Performance")
|
||||
local Element = require('modules.Element')
|
||||
|
||||
@@ -11,6 +11,16 @@ local Theme = require("modules.Theme")
|
||||
local Blur = require("modules.Blur")
|
||||
local utils = require("modules.utils")
|
||||
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")
|
||||
|
||||
-- Initialize ErrorHandler
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
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")
|
||||
local lu = require("testing.luaunit")
|
||||
|
||||
|
||||
@@ -9,9 +9,17 @@ require("testing.loveStub")
|
||||
local luaunit = require("testing.luaunit")
|
||||
local Units = require("modules.Units")
|
||||
local Context = require("modules.Context")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
local Calc = require("modules.Calc")
|
||||
|
||||
-- Initialize Units module with Context
|
||||
Units.init({ Context = Context })
|
||||
-- Initialize ErrorHandler
|
||||
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
|
||||
local MOCK_VIEWPORT_WIDTH = 1920
|
||||
|
||||
@@ -56,6 +56,7 @@ local testFiles = {
|
||||
"testing/__tests__/theme_test.lua",
|
||||
"testing/__tests__/units_test.lua",
|
||||
"testing/__tests__/utils_test.lua",
|
||||
"testing/__tests__/calc_test.lua",
|
||||
-- Feature/Integration tests
|
||||
"testing/__tests__/critical_failures_test.lua",
|
||||
"testing/__tests__/flexlove_test.lua",
|
||||
|
||||
Reference in New Issue
Block a user