674 lines
19 KiB
Lua
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
|