Files
FlexLove/testing/__tests__/texteditor_extended_coverage_test.lua
2025-11-20 20:07:04 -05:00

674 lines
19 KiB
Lua

-- Extended coverage tests for TextEditor module
-- Focuses on uncovered code paths to increase coverage
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local TextEditor = require("modules.TextEditor")
local Color = require("modules.Color")
local utils = require("modules.utils")
-- Mock dependencies
local MockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local MockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state) end,
}
-- Helper to create TextEditor
local function createTextEditor(config)
config = config or {}
return TextEditor.new(config, {
Context = MockContext,
StateManager = MockStateManager,
Color = Color,
utils = utils,
})
end
-- Helper to create mock element with renderer
local function createMockElement(width, height)
return {
_stateId = "test-element",
width = width or 200,
height = height or 100,
padding = {top = 5, right = 5, bottom = 5, left = 5},
x = 10,
y = 10,
_absoluteX = 10,
_absoluteY = 10,
_borderBoxWidth = (width or 200) + 10,
_borderBoxHeight = (height or 100) + 10,
getScaledContentPadding = function(self)
return self.padding
end,
_renderer = {
getFont = function(self, element)
return {
getWidth = function(text) return #text * 8 end,
getHeight = function() return 16 end,
}
end,
wrapLine = function(element, line, maxWidth)
-- Simple word wrapping
line = tostring(line or "")
maxWidth = tonumber(maxWidth) or 1000
local words = {}
for word in line:gmatch("%S+") do
table.insert(words, word)
end
local wrapped = {}
local currentLine = ""
local startIdx = 0
for i, word in ipairs(words) do
local testLine = currentLine == "" and word or (currentLine .. " " .. word)
if #testLine * 8 <= maxWidth then
currentLine = testLine
else
if currentLine ~= "" then
table.insert(wrapped, {text = currentLine, startIdx = startIdx, endIdx = startIdx + #currentLine})
startIdx = startIdx + #currentLine + 1
end
currentLine = word
end
end
if currentLine ~= "" then
table.insert(wrapped, {text = currentLine, startIdx = startIdx, endIdx = startIdx + #currentLine})
end
return #wrapped > 0 and wrapped or {{text = line, startIdx = 0, endIdx = #line}}
end,
},
}
end
-- ============================================================================
-- Auto-grow Height Tests
-- ============================================================================
TestTextEditorAutoGrow = {}
function TestTextEditorAutoGrow:test_updateAutoGrowHeight_single_line()
local editor = createTextEditor({
multiline = false,
autoGrow = true,
text = "Single line"
})
local element = createMockElement()
editor:initialize(element)
editor:updateAutoGrowHeight()
-- Single line should not trigger height change
luaunit.assertNotNil(element.height)
end
function TestTextEditorAutoGrow:test_updateAutoGrowHeight_multiline()
local editor = createTextEditor({
multiline = true,
autoGrow = true,
text = "Line 1\nLine 2\nLine 3"
})
local element = createMockElement(200, 50)
editor:initialize(element)
local initialHeight = element.height
editor:updateAutoGrowHeight()
-- Height should be updated based on line count
luaunit.assertNotNil(element.height)
end
function TestTextEditorAutoGrow:test_updateAutoGrowHeight_with_wrapping()
local editor = createTextEditor({
multiline = true,
autoGrow = true,
textWrap = "word",
text = "This is a very long line that will wrap multiple times when displayed"
})
local element = createMockElement(100, 50)
editor:initialize(element)
editor:updateAutoGrowHeight()
-- Should account for wrapped lines
luaunit.assertNotNil(element.height)
end
-- ============================================================================
-- Update and Cursor Blink Tests
-- ============================================================================
TestTextEditorUpdate = {}
function TestTextEditorUpdate:test_update_cursor_blink_pause_resume()
local editor = createTextEditor({text = "Test"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:_resetCursorBlink(true) -- Pause
luaunit.assertTrue(editor._cursorBlinkPaused)
-- Update to resume blink
editor:update(0.6) -- More than 0.5 second pause
luaunit.assertFalse(editor._cursorBlinkPaused)
end
function TestTextEditorUpdate:test_update_not_focused()
local editor = createTextEditor({text = "Test"})
local element = createMockElement()
editor:initialize(element)
-- Not focused - update should exit early
editor:update(0.1)
luaunit.assertTrue(true) -- Should not crash
end
function TestTextEditorUpdate:test_cursor_blink_cycle()
local editor = createTextEditor({text = "Test", cursorBlinkRate = 0.5})
local element = createMockElement()
editor:initialize(element)
editor:focus()
local initialVisible = editor._cursorVisible
-- Complete a full blink cycle
editor:update(0.5)
luaunit.assertNotEquals(editor._cursorVisible, initialVisible)
end
-- ============================================================================
-- Selection Rectangle Calculation Tests
-- ============================================================================
TestTextEditorSelectionRects = {}
function TestTextEditorSelectionRects:test_getSelectionRects_single_line()
local editor = createTextEditor({text = "Hello World", multiline = false})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 5)
local rects = editor:_getSelectionRects(0, 5)
luaunit.assertNotNil(rects)
luaunit.assertTrue(#rects > 0)
luaunit.assertNotNil(rects[1].x)
luaunit.assertNotNil(rects[1].y)
luaunit.assertNotNil(rects[1].width)
luaunit.assertNotNil(rects[1].height)
end
function TestTextEditorSelectionRects:test_getSelectionRects_multiline()
local editor = createTextEditor({text = "Line 1\nLine 2\nLine 3", multiline = true})
local element = createMockElement()
editor:initialize(element)
-- Select across lines
editor:setSelection(0, 14) -- "Line 1\nLine 2"
local rects = editor:_getSelectionRects(0, 14)
luaunit.assertNotNil(rects)
luaunit.assertTrue(#rects > 0)
end
function TestTextEditorSelectionRects:test_getSelectionRects_with_wrapping()
local editor = createTextEditor({
text = "This is a long line that wraps",
multiline = true,
textWrap = "word"
})
local element = createMockElement(100, 100)
editor:initialize(element)
editor:setSelection(0, 20)
local rects = editor:_getSelectionRects(0, 20)
luaunit.assertNotNil(rects)
end
function TestTextEditorSelectionRects:test_getSelectionRects_password_mode()
local editor = createTextEditor({
text = "secret",
passwordMode = true,
multiline = false
})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 6)
local rects = editor:_getSelectionRects(0, 6)
luaunit.assertNotNil(rects)
luaunit.assertTrue(#rects > 0)
end
-- ============================================================================
-- Cursor Screen Position Tests
-- ============================================================================
TestTextEditorCursorPosition = {}
function TestTextEditorCursorPosition:test_getCursorScreenPosition_single_line()
local editor = createTextEditor({text = "Hello", multiline = false})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(3)
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertNotNil(y)
luaunit.assertTrue(x >= 0)
luaunit.assertEquals(y, 0)
end
function TestTextEditorCursorPosition:test_getCursorScreenPosition_multiline()
local editor = createTextEditor({text = "Line 1\nLine 2", multiline = true})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(10) -- Second line
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertNotNil(y)
end
function TestTextEditorCursorPosition:test_getCursorScreenPosition_with_wrapping()
local editor = createTextEditor({
text = "Very long text that will wrap",
multiline = true,
textWrap = "word"
})
local element = createMockElement(100, 100)
editor:initialize(element)
editor:setCursorPosition(15)
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertNotNil(y)
end
function TestTextEditorCursorPosition:test_getCursorScreenPosition_password_mode()
local editor = createTextEditor({
text = "password123",
passwordMode = true
})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(8)
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertEquals(y, 0)
end
-- ============================================================================
-- Text Scroll Tests
-- ============================================================================
TestTextEditorScroll = {}
function TestTextEditorScroll:test_updateTextScroll()
local editor = createTextEditor({text = "This is very long text that needs scrolling"})
local element = createMockElement(100, 30)
editor:initialize(element)
editor:focus()
editor:moveCursorToEnd()
editor:_updateTextScroll()
-- Scroll should be updated
luaunit.assertTrue(editor._textScrollX >= 0)
end
function TestTextEditorScroll:test_updateTextScroll_keeps_cursor_visible()
local editor = createTextEditor({text = "Long text here"})
local element = createMockElement(50, 30)
editor:initialize(element)
editor:focus()
editor:setCursorPosition(10)
editor:_updateTextScroll()
local scrollX = editor._textScrollX
luaunit.assertTrue(scrollX >= 0)
end
-- ============================================================================
-- Mouse Interaction Edge Cases
-- ============================================================================
TestTextEditorMouseEdgeCases = {}
function TestTextEditorMouseEdgeCases:test_handleTextDrag_sets_flag()
local editor = createTextEditor({text = "Drag me"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleTextClick(10, 10, 1)
editor:handleTextDrag(50, 10)
luaunit.assertTrue(editor._textDragOccurred or not editor:hasSelection())
end
function TestTextEditorMouseEdgeCases:test_mouseToTextPosition_multiline()
local editor = createTextEditor({text = "Line 1\nLine 2\nLine 3", multiline = true})
local element = createMockElement()
editor:initialize(element)
-- Click on second line
local pos = editor:mouseToTextPosition(20, 25)
luaunit.assertNotNil(pos)
luaunit.assertTrue(pos >= 0) -- Valid position
end
function TestTextEditorMouseEdgeCases:test_mouseToTextPosition_with_scroll()
local editor = createTextEditor({text = "Very long scrolling text"})
local element = createMockElement(100, 30)
editor:initialize(element)
editor:focus()
editor._textScrollX = 50
local pos = editor:mouseToTextPosition(30, 15)
luaunit.assertNotNil(pos)
end
-- ============================================================================
-- Word Selection Tests
-- ============================================================================
TestTextEditorWordSelection = {}
function TestTextEditorWordSelection:test_selectWordAtPosition()
local editor = createTextEditor({text = "Hello World Test"})
local element = createMockElement()
editor:initialize(element)
editor:_selectWordAtPosition(7) -- "World"
luaunit.assertTrue(editor:hasSelection())
local selected = editor:getSelectedText()
luaunit.assertEquals(selected, "World")
end
function TestTextEditorWordSelection:test_selectWordAtPosition_with_punctuation()
local editor = createTextEditor({text = "Hello, World!"})
local element = createMockElement()
editor:initialize(element)
editor:_selectWordAtPosition(7) -- "World"
local selected = editor:getSelectedText()
luaunit.assertEquals(selected, "World")
end
function TestTextEditorWordSelection:test_selectWordAtPosition_empty()
local editor = createTextEditor({text = ""})
local element = createMockElement()
editor:initialize(element)
editor:_selectWordAtPosition(0)
-- Should not crash
luaunit.assertFalse(editor:hasSelection())
end
-- ============================================================================
-- State Saving Tests
-- ============================================================================
TestTextEditorStateSaving = {}
function TestTextEditorStateSaving:test_saveState_immediate_mode()
local savedState = nil
local mockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state)
savedState = state
end,
}
local mockContext = {
_immediateMode = true,
_focusedElement = nil,
}
local editor = TextEditor.new({text = "Test"}, {
Context = mockContext,
StateManager = mockStateManager,
Color = Color,
utils = utils,
})
local element = createMockElement()
element._stateId = "test-state-id"
editor:initialize(element)
editor:setText("New text")
luaunit.assertNotNil(savedState)
luaunit.assertEquals(savedState._textBuffer, "New text")
end
function TestTextEditorStateSaving:test_saveState_not_immediate_mode()
local saveCalled = false
local mockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state)
saveCalled = true
end,
}
local mockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local editor = TextEditor.new({text = "Test"}, {
Context = mockContext,
StateManager = mockStateManager,
Color = Color,
utils = utils,
})
local element = createMockElement()
editor:initialize(element)
editor:_saveState()
-- Should not save in retained mode
luaunit.assertFalse(saveCalled)
end
-- ============================================================================
-- Text Wrapping Edge Cases
-- ============================================================================
TestTextEditorWrappingEdgeCases = {}
function TestTextEditorWrappingEdgeCases:test_wrapLine_empty_line()
local editor = createTextEditor({multiline = true, textWrap = "word"})
local element = createMockElement()
editor:initialize(element)
local wrapped = editor:_wrapLine("", 100)
luaunit.assertNotNil(wrapped)
luaunit.assertTrue(#wrapped > 0)
end
function TestTextEditorWrappingEdgeCases:test_calculateWrapping_empty_lines()
local editor = createTextEditor({
multiline = true,
textWrap = "word",
text = "Line 1\n\nLine 3"
})
local element = createMockElement()
editor:initialize(element)
editor:_calculateWrapping()
luaunit.assertNotNil(editor._wrappedLines)
end
function TestTextEditorWrappingEdgeCases:test_calculateWrapping_no_element()
local editor = createTextEditor({
multiline = true,
textWrap = "word",
text = "Test"
})
-- No element initialized
editor:_calculateWrapping()
luaunit.assertNil(editor._wrappedLines)
end
-- ============================================================================
-- Insert Text Edge Cases
-- ============================================================================
TestTextEditorInsertEdgeCases = {}
function TestTextEditorInsertEdgeCases:test_insertText_empty_after_sanitization()
local editor = createTextEditor({
maxLength = 5,
text = "12345"
})
local element = createMockElement()
editor:initialize(element)
-- Try to insert when at max length
editor:insertText("67890")
-- Should not insert anything
luaunit.assertEquals(editor:getText(), "12345")
end
function TestTextEditorInsertEdgeCases:test_insertText_updates_cursor()
local editor = createTextEditor({text = "Hello"})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(5)
editor:insertText(" World")
luaunit.assertEquals(editor:getCursorPosition(), 11)
end
-- ============================================================================
-- Keyboard Input Edge Cases
-- ============================================================================
TestTextEditorKeyboardEdgeCases = {}
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_escape_with_selection()
local editor = createTextEditor({text = "Select me"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:selectAll()
editor:handleKeyPress("escape", "escape", false)
luaunit.assertFalse(editor:hasSelection())
end
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_escape_without_selection()
local editor = createTextEditor({text = "Test"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleKeyPress("escape", "escape", false)
luaunit.assertFalse(editor:isFocused())
end
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_arrow_with_shift()
local editor = createTextEditor({text = "Select this"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(0)
-- Simulate shift+right arrow
love.keyboard.setDown("lshift", true)
editor:handleKeyPress("right", "right", false)
love.keyboard.setDown("lshift", false)
luaunit.assertTrue(editor:hasSelection())
end
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_ctrl_backspace()
local editor = createTextEditor({text = "Delete this"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(11)
-- Simulate ctrl+backspace
love.keyboard.setDown("lctrl", true)
editor:handleKeyPress("backspace", "backspace", false)
love.keyboard.setDown("lctrl", false)
luaunit.assertEquals(editor:getText(), "")
end
-- ============================================================================
-- Focus Edge Cases
-- ============================================================================
TestTextEditorFocusEdgeCases = {}
function TestTextEditorFocusEdgeCases:test_focus_blurs_previous()
local editor1 = createTextEditor({text = "Editor 1"})
local editor2 = createTextEditor({text = "Editor 2"})
local element1 = createMockElement()
local element2 = createMockElement()
element1._textEditor = editor1
element2._textEditor = editor2
editor1:initialize(element1)
editor2:initialize(element2)
MockContext._focusedElement = element1
editor1:focus()
-- Focus second editor
editor2:focus()
luaunit.assertFalse(editor1:isFocused())
luaunit.assertTrue(editor2:isFocused())
end
-- Run tests
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end