multi-line selection fixed

This commit is contained in:
Michael Freno
2025-11-09 10:22:25 -05:00
parent 0a88c952bb
commit 3690202f48
2 changed files with 583 additions and 37 deletions

View File

@@ -1776,5 +1776,313 @@ function TestInputField:testTextScrollWithBackspace()
lu.assertTrue(element._textScrollX <= initialScroll)
end
-- ====================
-- Multiline Text Selection Tests
-- ====================
function TestInputField:testMultilineMouseToTextPositionBasic()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "Line 1\nLine 2\nLine 3",
})
element:focus()
-- Get font to calculate positions
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Click at start (should be position 0)
local pos = element:_mouseToTextPosition(10, 10)
lu.assertEquals(pos, 0)
-- Click on second line start (should be after "Line 1\n" = position 7)
pos = element:_mouseToTextPosition(10, 10 + lineHeight)
lu.assertEquals(pos, 7)
-- Click on third line start (should be after "Line 1\nLine 2\n" = position 14)
pos = element:_mouseToTextPosition(10, 10 + lineHeight * 2)
lu.assertEquals(pos, 14)
end
function TestInputField:testMultilineMouseToTextPositionXCoordinate()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "ABC\nDEF\nGHI",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
local charWidth = font:getWidth("A")
-- Click in middle of first line (should be around position 1-2)
local pos = element:_mouseToTextPosition(10 + charWidth * 1.5, 10)
lu.assertTrue(pos >= 1 and pos <= 2)
-- Click at end of second line (should be around position 6-7)
-- Text is "ABC\nDEF\nGHI", so second line "DEF" ends at position 6 or 7 (newline)
pos = element:_mouseToTextPosition(10 + charWidth * 3, 10 + lineHeight)
lu.assertTrue(pos >= 6 and pos <= 7)
end
function TestInputField:testMultilineMouseDragSelection()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "Line 1\nLine 2\nLine 3",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Simulate mouse click on first line (sets _mouseDownPosition)
element:_handleTextClick(10, 10, 1)
lu.assertEquals(element._cursorPosition, 0)
lu.assertFalse(element:hasSelection())
-- Drag to second line
element:_handleTextDrag(50, 10 + lineHeight)
lu.assertTrue(element:hasSelection())
-- Selection should span from first line to second line
local startPos, endPos = element:getSelection()
lu.assertTrue(startPos == 0 or endPos == 0)
lu.assertTrue(startPos > 6 or endPos > 6) -- Past first newline
-- After drag, selection should be preserved
lu.assertTrue(element:hasSelection())
end
function TestInputField:testMultilineMouseDragAcrossThreeLines()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 150,
editable = true,
multiline = true,
text = "First\nSecond\nThird\nFourth",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Click on first line, then drag to third line
element:_handleTextClick(10, 10, 1)
element:_handleTextDrag(50, 10 + lineHeight * 2.5)
lu.assertTrue(element:hasSelection())
local startPos, endPos = element:getSelection()
-- Should select across multiple lines
local minPos = math.min(startPos, endPos)
local maxPos = math.max(startPos, endPos)
lu.assertEquals(minPos, 0) -- From start
lu.assertTrue(maxPos > 12) -- Past "First\nSecond\n"
-- Selection should persist after drag
lu.assertTrue(element:hasSelection())
end
function TestInputField:testMultilineClickOnDifferentLines()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "AAA\nBBB\nCCC",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Click on first line
element:_handleTextClick(10, 10, 1)
local pos1 = element._cursorPosition
lu.assertEquals(pos1, 0)
-- Click on second line
element:_handleTextClick(10, 10 + lineHeight, 1)
local pos2 = element._cursorPosition
lu.assertEquals(pos2, 4) -- After "AAA\n"
-- Click on third line
element:_handleTextClick(10, 10 + lineHeight * 2, 1)
local pos3 = element._cursorPosition
lu.assertEquals(pos3, 8) -- After "AAA\nBBB\n"
end
function TestInputField:testMultilineSelectionWithKeyboard()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "Line 1\nLine 2\nLine 3",
})
element:focus()
element:setCursorPosition(0)
-- Mock Shift key
local oldIsDown = _G.love.keyboard.isDown
_G.love.keyboard.isDown = function(...)
local keys = {...}
for _, key in ipairs(keys) do
if key == "lshift" or key == "rshift" then
return true
end
end
return false
end
-- Test Shift+Right selection (horizontal movement works)
element:keypressed("right", nil, false)
lu.assertTrue(element:hasSelection())
local startPos, endPos = element:getSelection()
lu.assertTrue(math.abs(endPos - startPos) > 0)
-- Reset mock
_G.love.keyboard.isDown = oldIsDown
end
function TestInputField:testMultilineMouseSelectionPreservedAfterRelease()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "First line\nSecond line\nThird line",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Create a selection by dragging
element:_handleTextClick(10, 10, 1)
element:_handleTextDrag(100, 10 + lineHeight * 1.5)
local startPos, endPos = element:getSelection()
local hadSelection = element:hasSelection()
lu.assertTrue(hadSelection)
-- Note: There's no mouse release handler that affects selection
-- The drag creates the selection and it persists
-- Selection should still exist
lu.assertTrue(element:hasSelection())
local startPos2, endPos2 = element:getSelection()
lu.assertEquals(startPos, startPos2)
lu.assertEquals(endPos, endPos2)
end
function TestInputField:testMultilineClickDoesNotPreserveSelection()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 100,
editable = true,
multiline = true,
text = "First line\nSecond line",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Create a selection
element:setSelection(0, 5)
lu.assertTrue(element:hasSelection())
-- Click somewhere else (should clear selection)
element:_handleTextClick(10, 10 + lineHeight, 1)
-- Selection should be cleared
lu.assertFalse(element:hasSelection())
end
function TestInputField:testMultilineEmptyLinesHandling()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 150,
editable = true,
multiline = true,
text = "Line 1\n\nLine 3",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Click on empty line (second line)
local pos = element:_mouseToTextPosition(10, 10 + lineHeight)
lu.assertEquals(pos, 7) -- After "Line 1\n"
-- Click on third line
pos = element:_mouseToTextPosition(10, 10 + lineHeight * 2)
lu.assertEquals(pos, 8) -- After "Line 1\n\n"
end
function TestInputField:testMultilineYCoordinateBeyondText()
local element = FlexLove.Element.new({
x = 10,
y = 10,
width = 300,
height = 200,
editable = true,
multiline = true,
text = "Line 1\nLine 2",
})
element:focus()
local font = element:_getFont()
local lineHeight = font:getHeight()
-- Click way below the text (should clamp to last line)
local pos = element:_mouseToTextPosition(10, 10 + lineHeight * 10)
local textLen = utf8.len(element.text)
-- Should be at or near end of text
lu.assertTrue(pos >= textLen - 6) -- Within last line
end
-- Run tests
lu.LuaUnit.run()