local lu = require("testing.luaunit") local FlexLove = require("FlexLove") local Gui = FlexLove.Gui local Element = FlexLove.Element TestKeyboardInput = {} -- Helper function to ensure clean keyboard state local function clearModifierKeys() love.keyboard.setDown("lshift", false) love.keyboard.setDown("rshift", false) love.keyboard.setDown("lctrl", false) love.keyboard.setDown("rctrl", false) love.keyboard.setDown("lalt", false) love.keyboard.setDown("ralt", false) love.keyboard.setDown("lgui", false) love.keyboard.setDown("rgui", false) end function TestKeyboardInput:setUp() -- Clear all keyboard modifier states at start of each test if love.keyboard.isDown("lgui", "rgui", "lalt", "ralt", "lctrl", "rctrl", "lshift", "rshift") then local mods = {} if love.keyboard.isDown("lshift") then table.insert(mods, "lshift") end if love.keyboard.isDown("rshift") then table.insert(mods, "rshift") end if love.keyboard.isDown("lctrl") then table.insert(mods, "lctrl") end if love.keyboard.isDown("rctrl") then table.insert(mods, "rctrl") end if love.keyboard.isDown("lalt") then table.insert(mods, "lalt") end if love.keyboard.isDown("ralt") then table.insert(mods, "ralt") end if love.keyboard.isDown("lgui") then table.insert(mods, "lgui") end if love.keyboard.isDown("rgui") then table.insert(mods, "rgui") end print("WARNING: Modifiers down at setUp: " .. table.concat(mods, ", ")) end love.keyboard.setDown("lshift", false) love.keyboard.setDown("rshift", false) love.keyboard.setDown("lctrl", false) love.keyboard.setDown("rctrl", false) love.keyboard.setDown("lalt", false) love.keyboard.setDown("ralt", false) love.keyboard.setDown("lgui", false) love.keyboard.setDown("rgui", false) Gui.init({ baseScale = { width = 1920, height = 1080 } }) end function TestKeyboardInput:tearDown() -- Clear all keyboard modifier states love.keyboard.setDown("lshift", false) love.keyboard.setDown("rshift", false) love.keyboard.setDown("lctrl", false) love.keyboard.setDown("rctrl", false) love.keyboard.setDown("lalt", false) love.keyboard.setDown("ralt", false) love.keyboard.setDown("lgui", false) love.keyboard.setDown("rgui", false) Gui.destroy() end -- ==================== -- Focus Management Tests -- ==================== function TestKeyboardInput:testFocusEditable() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) lu.assertFalse(input:isFocused()) input:focus() lu.assertTrue(input:isFocused()) lu.assertEquals(Gui._focusedElement, input) end function TestKeyboardInput:testBlurEditable() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() lu.assertTrue(input:isFocused()) input:blur() lu.assertFalse(input:isFocused()) lu.assertNil(Gui._focusedElement) end function TestKeyboardInput:testFocusSwitching() local input1 = Element.new({ width = 200, height = 40, editable = true, text = "Input 1", }) local input2 = Element.new({ width = 200, height = 40, editable = true, text = "Input 2", }) input1:focus() lu.assertTrue(input1:isFocused()) lu.assertFalse(input2:isFocused()) input2:focus() lu.assertFalse(input1:isFocused()) lu.assertTrue(input2:isFocused()) lu.assertEquals(Gui._focusedElement, input2) end function TestKeyboardInput:testSelectOnFocus() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", selectOnFocus = true, }) lu.assertFalse(input:hasSelection()) input:focus() lu.assertTrue(input:hasSelection()) local startPos, endPos = input:getSelection() lu.assertEquals(startPos, 0) lu.assertEquals(endPos, 11) -- Length of "Hello World" end -- ==================== -- Text Input Tests -- ==================== function TestKeyboardInput:testTextInput() local input = Element.new({ width = 200, height = 40, editable = true, text = "", }) input:focus() input:textinput("H") input:textinput("i") lu.assertEquals(input:getText(), "Hi") lu.assertEquals(input._cursorPosition, 2) end function TestKeyboardInput:testTextInputAtPosition() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(2) -- After "He" input:textinput("X") lu.assertEquals(input:getText(), "HeXllo") lu.assertEquals(input._cursorPosition, 3) end function TestKeyboardInput:testTextInputWithSelection() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", }) input:focus() input:setSelection(0, 5) -- Select "Hello" input:textinput("Hi") lu.assertEquals(input:getText(), "Hi World") lu.assertEquals(input._cursorPosition, 2) lu.assertFalse(input:hasSelection()) end function TestKeyboardInput:testMaxLengthConstraint() local input = Element.new({ width = 200, height = 40, editable = true, text = "", maxLength = 5, }) input:focus() input:textinput("Hello") lu.assertEquals(input:getText(), "Hello") input:textinput("X") -- Should not be added lu.assertEquals(input:getText(), "Hello") end -- ==================== -- Backspace/Delete Tests -- ==================== function TestKeyboardInput:testBackspace() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(5) -- At end input:keypressed("backspace", "backspace", false) lu.assertEquals(input:getText(), "Hell") lu.assertEquals(input._cursorPosition, 4) end function TestKeyboardInput:testBackspaceAtStart() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(0) -- At start input:keypressed("backspace", "backspace", false) lu.assertEquals(input:getText(), "Hello") -- No change lu.assertEquals(input._cursorPosition, 0) end function TestKeyboardInput:testDelete() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(0) -- At start input:keypressed("delete", "delete", false) lu.assertEquals(input:getText(), "ello") lu.assertEquals(input._cursorPosition, 0) end function TestKeyboardInput:testDeleteAtEnd() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(5) -- At end input:keypressed("delete", "delete", false) lu.assertEquals(input:getText(), "Hello") -- No change lu.assertEquals(input._cursorPosition, 5) end function TestKeyboardInput:testBackspaceWithSelection() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", }) input:focus() input:setSelection(0, 5) -- Select "Hello" input:keypressed("backspace", "backspace", false) lu.assertEquals(input:getText(), " World") lu.assertEquals(input._cursorPosition, 0) lu.assertFalse(input:hasSelection()) end -- ==================== -- Cursor Movement Tests -- ==================== function TestKeyboardInput:testArrowLeft() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(5) input:keypressed("left", "left", false) lu.assertEquals(input._cursorPosition, 4) end function TestKeyboardInput:testArrowRight() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(0) input:keypressed("right", "right", false) lu.assertEquals(input._cursorPosition, 1) end function TestKeyboardInput:testHomeKey() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(5) input:keypressed("home", "home", false) lu.assertEquals(input._cursorPosition, 0) end function TestKeyboardInput:testEndKey() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(0) input:keypressed("end", "end", false) lu.assertEquals(input._cursorPosition, 5) end -- ==================== -- Modifier Key Tests -- ==================== function TestKeyboardInput:testSuperLeftMovesToStart() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", }) input:focus() input:setCursorPosition(8) -- Middle of text -- Simulate Super (Cmd/Win) key being held love.keyboard.setDown("lgui", true) input:keypressed("left", "left", false) love.keyboard.setDown("lgui", false) lu.assertEquals(input._cursorPosition, 0) end function TestKeyboardInput:testSuperRightMovesToEnd() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", }) input:focus() input:setCursorPosition(3) -- Middle of text -- Simulate Super (Cmd/Win) key being held love.keyboard.setDown("lgui", true) input:keypressed("right", "right", false) love.keyboard.setDown("lgui", false) lu.assertEquals(input._cursorPosition, 11) -- End of "Hello World" end function TestKeyboardInput:testAltLeftMovesByWord() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World Test", }) input:focus() input:setCursorPosition(16) -- End of text -- Simulate Alt key being held and move left by word love.keyboard.setDown("lalt", true) input:keypressed("left", "left", false) love.keyboard.setDown("lalt", false) lu.assertEquals(input._cursorPosition, 12) -- Start of "Test" end function TestKeyboardInput:testAltRightMovesByWord() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World Test", }) input:focus() input:setCursorPosition(0) -- Start of text -- Simulate Alt key being held and move right by word love.keyboard.setDown("lalt", true) input:keypressed("right", "right", false) love.keyboard.setDown("lalt", false) lu.assertEquals(input._cursorPosition, 6) -- After "Hello " end function TestKeyboardInput:testAltLeftMultipleWords() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "The quick brown fox", }) input:focus() input:setCursorPosition(19) -- End of text -- Move left by word three times love.keyboard.setDown("lalt", true) input:keypressed("left", "left", false) -- to "fox" input:keypressed("left", "left", false) -- to "brown" input:keypressed("left", "left", false) -- to "quick" love.keyboard.setDown("lalt", false) lu.assertEquals(input._cursorPosition, 4) -- Start of "quick" end function TestKeyboardInput:testAltRightMultipleWords() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "The quick brown fox", }) input:focus() input:setCursorPosition(0) -- Start of text -- Move right by word three times love.keyboard.setDown("lalt", true) input:keypressed("right", "right", false) -- after "The " input:keypressed("right", "right", false) -- after "quick " input:keypressed("right", "right", false) -- after "brown " love.keyboard.setDown("lalt", false) lu.assertEquals(input._cursorPosition, 16) -- After "brown " end function TestKeyboardInput:testSuperLeftWithSelection() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", }) input:focus() input:setSelection(3, 8) lu.assertTrue(input:hasSelection()) -- Super+Left should move to start and clear selection love.keyboard.setDown("lgui", true) input:keypressed("left", "left", false) love.keyboard.setDown("lgui", false) lu.assertEquals(input._cursorPosition, 0) lu.assertFalse(input:hasSelection()) end function TestKeyboardInput:testSuperRightWithSelection() clearModifierKeys() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello World", }) input:focus() input:setSelection(3, 8) lu.assertTrue(input:hasSelection()) -- Super+Right should move to end and clear selection love.keyboard.setDown("lgui", true) input:keypressed("right", "right", false) love.keyboard.setDown("lgui", false) lu.assertEquals(input._cursorPosition, 11) lu.assertFalse(input:hasSelection()) end function TestKeyboardInput:testEscapeClearsSelection() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:selectAll() lu.assertTrue(input:hasSelection()) input:keypressed("escape", "escape", false) lu.assertFalse(input:hasSelection()) lu.assertTrue(input:isFocused()) -- Still focused end function TestKeyboardInput:testEscapeBlurs() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() lu.assertTrue(input:isFocused()) input:keypressed("escape", "escape", false) lu.assertFalse(input:isFocused()) end -- ==================== -- Callback Tests -- ==================== function TestKeyboardInput:testOnTextChangeCallback() local changeCount = 0 local oldTextValue = nil local newTextValue = nil local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", onTextChange = function(element, newText, oldText) changeCount = changeCount + 1 newTextValue = newText oldTextValue = oldText end, }) input:focus() input:textinput("X") lu.assertEquals(changeCount, 1) lu.assertEquals(oldTextValue, "Hello") lu.assertEquals(newTextValue, "HelloX") end function TestKeyboardInput:testOnTextInputCallback() local inputCount = 0 local lastChar = nil local input = Element.new({ width = 200, height = 40, editable = true, text = "", onTextInput = function(element, text) inputCount = inputCount + 1 lastChar = text end, }) input:focus() input:textinput("A") input:textinput("B") lu.assertEquals(inputCount, 2) lu.assertEquals(lastChar, "B") end function TestKeyboardInput:testOnEnterCallback() local enterCalled = false local input = Element.new({ width = 200, height = 40, editable = true, multiline = false, text = "Hello", onEnter = function(element) enterCalled = true end, }) input:focus() input:keypressed("return", "return", false) lu.assertTrue(enterCalled) end function TestKeyboardInput:testOnFocusCallback() local focusCalled = false local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", onFocus = function(element) focusCalled = true end, }) input:focus() lu.assertTrue(focusCalled) end function TestKeyboardInput:testOnBlurCallback() local blurCalled = false local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", onBlur = function(element) blurCalled = true end, }) input:focus() input:blur() lu.assertTrue(blurCalled) end -- ==================== -- GUI-level Input Forwarding Tests -- ==================== function TestKeyboardInput:testGuiTextinputForwarding() local input = Element.new({ width = 200, height = 40, editable = true, text = "", }) input:focus() Gui.textinput("A") lu.assertEquals(input:getText(), "A") end function TestKeyboardInput:testGuiKeypressedForwarding() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) input:focus() input:setCursorPosition(5) Gui.keypressed("backspace", "backspace", false) lu.assertEquals(input:getText(), "Hell") end function TestKeyboardInput:testGuiInputWithoutFocus() local input = Element.new({ width = 200, height = 40, editable = true, text = "Hello", }) -- No focus Gui.textinput("X") lu.assertEquals(input:getText(), "Hello") -- No change end lu.LuaUnit.run()