package.path = package.path .. ";./?.lua;./modules/?.lua" 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") local ErrorHandler = require("modules.ErrorHandler") local ScrollManager = require("modules.ScrollManager") local Color = require("modules.Color") local utils = require("modules.utils") FlexLove.init() local InputEvent = package.loaded["modules.InputEvent"] local GestureRecognizer = package.loaded["modules.GestureRecognizer"] -- ============================================================================ -- Helpers -- ============================================================================ --- Create a GestureRecognizer touch event helper local function touchEvent(id, x, y, phase, time) if time then love.timer.setTime(time) end return InputEvent.fromTouch(id, x, y, phase, 1.0) end --- Create a ScrollManager with touch config (uses direct module init, not FlexLove) local function createTouchScrollManager(config) config = config or {} config.overflow = config.overflow or "scroll" ErrorHandler.init({}) ScrollManager.init({ ErrorHandler = ErrorHandler }) return ScrollManager.new(config, { Color = Color, utils = utils, }) end --- Create a mock element for ScrollManager tests local function createMockElement(width, height, contentWidth, contentHeight) local children = {} table.insert(children, { x = 0, y = 0, width = contentWidth or 200, height = contentHeight or 600, margin = { top = 0, right = 0, bottom = 0, left = 0 }, _explicitlyAbsolute = false, getBorderBoxWidth = function(self) return self.width end, getBorderBoxHeight = function(self) return self.height end, }) return { x = 0, y = 0, width = width or 200, height = height or 300, padding = { top = 0, right = 0, bottom = 0, left = 0 }, children = children, getBorderBoxWidth = function(self) return self.width end, getBorderBoxHeight = function(self) return self.height end, } end -- ============================================================================ -- InputEvent.fromTouch Tests -- ============================================================================ TestTouchInputEvent = {} function TestTouchInputEvent:test_fromTouch_creates_valid_touchpress() local event = InputEvent.fromTouch("touch1", 100, 200, "began", 0.8) luaunit.assertEquals(event.type, "touchpress") luaunit.assertEquals(event.x, 100) luaunit.assertEquals(event.y, 200) luaunit.assertEquals(event.touchId, "touch1") luaunit.assertEquals(event.pressure, 0.8) luaunit.assertEquals(event.phase, "began") luaunit.assertEquals(event.button, 1) end function TestTouchInputEvent:test_fromTouch_moved_phase() local event = InputEvent.fromTouch("touch1", 150, 250, "moved", 1.0) luaunit.assertEquals(event.type, "touchmove") luaunit.assertEquals(event.phase, "moved") end function TestTouchInputEvent:test_fromTouch_ended_phase() local event = InputEvent.fromTouch("touch1", 150, 250, "ended", 1.0) luaunit.assertEquals(event.type, "touchrelease") luaunit.assertEquals(event.phase, "ended") end function TestTouchInputEvent:test_fromTouch_cancelled_phase() local event = InputEvent.fromTouch("touch1", 150, 250, "cancelled", 1.0) luaunit.assertEquals(event.type, "touchcancel") luaunit.assertEquals(event.phase, "cancelled") end -- ============================================================================ -- EventHandler Touch Processing Tests -- ============================================================================ TestTouchEventHandler = {} function TestTouchEventHandler:test_touch_began() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() love.touch.getTouches = function() return { "touch1" } end love.touch.getPosition = function(id) if id == "touch1" then return 100, 100 end return 0, 0 end FlexLove.beginFrame() element._eventHandler:processTouchEvents(element) FlexLove.endFrame() local filteredEvents = {} for _, event in ipairs(touchEvents) do if event.type ~= "hover" and event.type ~= "unhover" then table.insert(filteredEvents, event) end end luaunit.assertTrue(#filteredEvents >= 1, "Should receive at least 1 touch event, got " .. #filteredEvents) luaunit.assertEquals(filteredEvents[1].type, "touchpress") luaunit.assertEquals(filteredEvents[1].touchId, "touch1") end function TestTouchEventHandler:test_touch_moved() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() love.touch.getTouches = function() return { "touch1" } end love.touch.getPosition = function(id) if id == "touch1" then return 100, 100 end return 0, 0 end FlexLove.beginFrame() element._eventHandler:processTouchEvents(element) FlexLove.endFrame() love.touch.getPosition = function(id) if id == "touch1" then return 150, 150 end return 0, 0 end FlexLove.beginFrame() element._eventHandler:processTouchEvents(element) FlexLove.endFrame() local filteredEvents = {} for _, event in ipairs(touchEvents) do if event.type ~= "hover" and event.type ~= "unhover" then table.insert(filteredEvents, event) end end luaunit.assertEquals(#filteredEvents, 2) luaunit.assertEquals(filteredEvents[1].type, "touchpress") luaunit.assertEquals(filteredEvents[2].type, "touchmove") luaunit.assertEquals(filteredEvents[2].dx, 50) luaunit.assertEquals(filteredEvents[2].dy, 50) end function TestTouchEventHandler:test_touch_ended() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() love.touch.getTouches = function() return { "touch1" } end love.touch.getPosition = function(id) if id == "touch1" then return 100, 100 end return 0, 0 end FlexLove.beginFrame() element._eventHandler:processTouchEvents(element) FlexLove.endFrame() love.touch.getTouches = function() return {} end FlexLove.beginFrame() element._eventHandler:processTouchEvents(element) FlexLove.endFrame() local filteredEvents = {} for _, event in ipairs(touchEvents) do if event.type ~= "hover" and event.type ~= "unhover" then table.insert(filteredEvents, event) end end luaunit.assertEquals(#filteredEvents, 2) luaunit.assertEquals(filteredEvents[1].type, "touchpress") luaunit.assertEquals(filteredEvents[2].type, "touchrelease") end function TestTouchEventHandler:test_multi_touch() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, multiTouchEnabled = true, onEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() love.touch.getTouches = function() return { "touch1", "touch2" } end love.touch.getPosition = function(id) if id == "touch1" then return 50, 50 end if id == "touch2" then return 150, 150 end return 0, 0 end FlexLove.beginFrame() element._eventHandler:processTouchEvents(element) FlexLove.endFrame() local filteredEvents = {} for _, event in ipairs(touchEvents) do if event.type ~= "hover" and event.type ~= "unhover" then table.insert(filteredEvents, event) end end luaunit.assertEquals(#filteredEvents, 2) luaunit.assertEquals(filteredEvents[1].type, "touchpress") luaunit.assertEquals(filteredEvents[2].type, "touchpress") luaunit.assertNotEquals(filteredEvents[1].touchId, filteredEvents[2].touchId) end function TestTouchEventHandler:test_gestureRecognizer_structural() local recognizer = GestureRecognizer.new({}, { InputEvent = InputEvent, utils = utils, }) local pressEvent = InputEvent.fromTouch("touch1", 100, 100, "began", 1.0) local releaseEvent = InputEvent.fromTouch("touch1", 102, 102, "ended", 1.0) recognizer:processTouchEvent(pressEvent) recognizer:processTouchEvent(releaseEvent) luaunit.assertNotNil(recognizer) end -- ============================================================================ -- GestureRecognizer Tests -- ============================================================================ TestTouchGestureRecognizer = {} function TestTouchGestureRecognizer:setUp() self.recognizer = GestureRecognizer.new({}, { InputEvent = InputEvent, utils = {} }) love.timer.setTime(0) end function TestTouchGestureRecognizer:tearDown() self.recognizer:reset() end -- Tap tests function TestTouchGestureRecognizer:test_tap_detected() local event1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(event1) local event2 = touchEvent("t1", 100, 100, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(event2) luaunit.assertNotNil(gestures, "Tap gesture should be detected") luaunit.assertEquals(gestures[1].type, "tap") luaunit.assertEquals(gestures[1].state, "ended") luaunit.assertEquals(gestures[1].x, 100) luaunit.assertEquals(gestures[1].y, 100) end function TestTouchGestureRecognizer:test_tap_not_detected_when_too_slow() local event1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(event1) local event2 = touchEvent("t1", 100, 100, "ended", 0.5) local gestures = self.recognizer:processTouchEvent(event2) if gestures then for _, g in ipairs(gestures) do luaunit.assertNotEquals(g.type, "tap", "Slow touch should not be tap") end end end function TestTouchGestureRecognizer:test_tap_not_detected_when_moved_too_far() local event1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(event1) local event2 = touchEvent("t1", 120, 120, "moved", 0.05) self.recognizer:processTouchEvent(event2) local event3 = touchEvent("t1", 120, 120, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(event3) if gestures then for _, g in ipairs(gestures) do if g.type == "tap" then local dx = 120 - 100 local dy = 120 - 100 local dist = math.sqrt(dx * dx + dy * dy) luaunit.assertTrue(dist >= 10, "Movement should exceed tap threshold") end end end end function TestTouchGestureRecognizer:test_double_tap_detected() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 100, "ended", 0.05) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t2", 100, 100, "began", 0.15) self.recognizer:processTouchEvent(e3) local e4 = touchEvent("t2", 100, 100, "ended", 0.2) local gestures = self.recognizer:processTouchEvent(e4) luaunit.assertNotNil(gestures, "Should detect gesture on second tap") local foundDoubleTap = false for _, g in ipairs(gestures) do if g.type == "double_tap" then foundDoubleTap = true end end luaunit.assertTrue(foundDoubleTap, "Should detect double-tap gesture") end function TestTouchGestureRecognizer:test_double_tap_not_detected_when_too_slow() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 100, "ended", 0.05) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t2", 100, 100, "began", 0.5) self.recognizer:processTouchEvent(e3) local e4 = touchEvent("t2", 100, 100, "ended", 0.55) local gestures = self.recognizer:processTouchEvent(e4) if gestures then for _, g in ipairs(gestures) do luaunit.assertNotEquals(g.type, "double_tap", "Too-slow second tap should not be double-tap") end end end -- Pan tests function TestTouchGestureRecognizer:test_pan_began() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 110, 110, "moved", 0.05) local gestures = self.recognizer:processTouchEvent(e2) luaunit.assertNotNil(gestures, "Pan should be detected") luaunit.assertEquals(gestures[1].type, "pan") luaunit.assertEquals(gestures[1].state, "began") end function TestTouchGestureRecognizer:test_pan_changed() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 110, 110, "moved", 0.05) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 120, 120, "moved", 0.1) local gestures = self.recognizer:processTouchEvent(e3) luaunit.assertNotNil(gestures) local panChanged = nil for _, g in ipairs(gestures) do if g.type == "pan" and g.state == "changed" then panChanged = g end end luaunit.assertNotNil(panChanged, "Should detect pan changed") luaunit.assertEquals(panChanged.dx, 20) luaunit.assertEquals(panChanged.dy, 20) end function TestTouchGestureRecognizer:test_pan_ended() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 110, 110, "moved", 0.05) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 120, 120, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(e3) luaunit.assertNotNil(gestures) local panEnded = nil for _, g in ipairs(gestures) do if g.type == "pan" and g.state == "ended" then panEnded = g end end luaunit.assertNotNil(panEnded, "Should detect pan ended") end function TestTouchGestureRecognizer:test_pan_not_detected_with_small_movement() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 102, 102, "moved", 0.05) local gestures = self.recognizer:processTouchEvent(e2) luaunit.assertNil(gestures, "Small movement should not trigger pan") end -- Swipe tests function TestTouchGestureRecognizer:test_swipe_right() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 200, 100, "moved", 0.1) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 200, 100, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(e3) luaunit.assertNotNil(gestures) local swipe = nil for _, g in ipairs(gestures) do if g.type == "swipe" then swipe = g end end luaunit.assertNotNil(swipe, "Should detect swipe") luaunit.assertEquals(swipe.direction, "right") end function TestTouchGestureRecognizer:test_swipe_left() local e1 = touchEvent("t1", 200, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 100, "moved", 0.1) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 100, 100, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(e3) luaunit.assertNotNil(gestures) local swipe = nil for _, g in ipairs(gestures) do if g.type == "swipe" then swipe = g end end luaunit.assertNotNil(swipe, "Should detect left swipe") luaunit.assertEquals(swipe.direction, "left") end function TestTouchGestureRecognizer:test_swipe_down() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 200, "moved", 0.1) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 100, 200, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(e3) luaunit.assertNotNil(gestures) local swipe = nil for _, g in ipairs(gestures) do if g.type == "swipe" then swipe = g end end luaunit.assertNotNil(swipe, "Should detect down swipe") luaunit.assertEquals(swipe.direction, "down") end function TestTouchGestureRecognizer:test_swipe_up() local e1 = touchEvent("t1", 100, 200, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 100, "moved", 0.1) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 100, 100, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(e3) luaunit.assertNotNil(gestures) local swipe = nil for _, g in ipairs(gestures) do if g.type == "swipe" then swipe = g end end luaunit.assertNotNil(swipe, "Should detect up swipe") luaunit.assertEquals(swipe.direction, "up") end function TestTouchGestureRecognizer:test_swipe_not_detected_when_too_slow() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 200, 100, "moved", 0.3) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 200, 100, "ended", 0.3) local gestures = self.recognizer:processTouchEvent(e3) if gestures then for _, g in ipairs(gestures) do luaunit.assertNotEquals(g.type, "swipe", "Slow movement should not be a swipe") end end end function TestTouchGestureRecognizer:test_swipe_not_detected_when_too_short() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 130, 100, "moved", 0.05) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 130, 100, "ended", 0.05) local gestures = self.recognizer:processTouchEvent(e3) if gestures then for _, g in ipairs(gestures) do luaunit.assertNotEquals(g.type, "swipe", "Short movement should not be a swipe") end end end -- Pinch tests function TestTouchGestureRecognizer:test_pinch_detected() local e1 = touchEvent("t1", 100, 200, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t2", 200, 200, "began", 0) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 50, 200, "moved", 0.1) self.recognizer:processTouchEvent(e3) local e4 = touchEvent("t2", 250, 200, "moved", 0.1) local gestures = self.recognizer:processTouchEvent(e4) luaunit.assertNotNil(gestures, "Pinch should be detected") local pinch = nil for _, g in ipairs(gestures) do if g.type == "pinch" then pinch = g end end luaunit.assertNotNil(pinch, "Should detect pinch gesture") luaunit.assertTrue(pinch.scale > 1.0, "Scale should be greater than 1.0 for spread") end function TestTouchGestureRecognizer:test_pinch_scale_decreases() local e1 = touchEvent("t1", 50, 200, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t2", 250, 200, "began", 0) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t1", 100, 200, "moved", 0.1) self.recognizer:processTouchEvent(e3) local e4 = touchEvent("t2", 200, 200, "moved", 0.1) local gestures = self.recognizer:processTouchEvent(e4) if gestures then local pinch = nil for _, g in ipairs(gestures) do if g.type == "pinch" then pinch = g end end if pinch then luaunit.assertTrue(pinch.scale < 1.0, "Scale should be less than 1.0 for pinch") end end end function TestTouchGestureRecognizer:test_pinch_not_detected_with_one_touch() local e1 = touchEvent("t1", 100, 200, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 150, 200, "moved", 0.1) local gestures = self.recognizer:processTouchEvent(e2) if gestures then for _, g in ipairs(gestures) do luaunit.assertNotEquals(g.type, "pinch", "Single touch should not trigger pinch") end end end -- Rotate tests function TestTouchGestureRecognizer:test_rotate_detected() local e1 = touchEvent("t1", 100, 200, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t2", 200, 200, "began", 0) self.recognizer:processTouchEvent(e2) local e3 = touchEvent("t2", 200, 150, "moved", 0.1) local gestures = self.recognizer:processTouchEvent(e3) if gestures then local rotate = nil for _, g in ipairs(gestures) do if g.type == "rotate" then rotate = g end end if rotate then luaunit.assertNotNil(rotate.rotation, "Rotate gesture should have rotation angle") end end end -- Return value tests function TestTouchGestureRecognizer:test_processTouchEvent_returns_nil_for_no_gesture() local e1 = touchEvent("t1", 100, 100, "began", 0) local gestures = self.recognizer:processTouchEvent(e1) luaunit.assertNil(gestures, "Press alone should not produce gesture") end function TestTouchGestureRecognizer:test_processTouchEvent_returns_gesture_array() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 100, "ended", 0.1) local gestures = self.recognizer:processTouchEvent(e2) luaunit.assertNotNil(gestures) luaunit.assertTrue(#gestures >= 1, "Should return array with at least 1 gesture") luaunit.assertEquals(type(gestures[1]), "table") luaunit.assertNotNil(gestures[1].type) end function TestTouchGestureRecognizer:test_processTouchEvent_ignores_no_touchId() local event = { type = "touchpress", x = 100, y = 100 } local gestures = self.recognizer:processTouchEvent(event) luaunit.assertNil(gestures) end function TestTouchGestureRecognizer:test_touchcancel_cleans_up() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) local e2 = touchEvent("t1", 100, 100, "cancelled", 0.1) self.recognizer:processTouchEvent(e2) luaunit.assertEquals(self.recognizer:_getTouchCount(), 0) end -- Reset tests function TestTouchGestureRecognizer:test_reset_clears_state() local e1 = touchEvent("t1", 100, 100, "began", 0) self.recognizer:processTouchEvent(e1) luaunit.assertTrue(self.recognizer:_getTouchCount() > 0) self.recognizer:reset() luaunit.assertEquals(self.recognizer:_getTouchCount(), 0) end -- Config tests function TestTouchGestureRecognizer:test_custom_config_overrides_defaults() local custom = GestureRecognizer.new({ tapMaxDuration = 1.0, panMinMovement = 20, }, { InputEvent = InputEvent, utils = {} }) luaunit.assertEquals(custom._config.tapMaxDuration, 1.0) luaunit.assertEquals(custom._config.panMinMovement, 20) luaunit.assertEquals(custom._config.swipeMinDistance, 50) end -- Type/state exports function TestTouchGestureRecognizer:test_gesture_types_exported() luaunit.assertEquals(GestureRecognizer.GestureType.TAP, "tap") luaunit.assertEquals(GestureRecognizer.GestureType.DOUBLE_TAP, "double_tap") luaunit.assertEquals(GestureRecognizer.GestureType.LONG_PRESS, "long_press") luaunit.assertEquals(GestureRecognizer.GestureType.SWIPE, "swipe") luaunit.assertEquals(GestureRecognizer.GestureType.PAN, "pan") luaunit.assertEquals(GestureRecognizer.GestureType.PINCH, "pinch") luaunit.assertEquals(GestureRecognizer.GestureType.ROTATE, "rotate") end function TestTouchGestureRecognizer:test_gesture_states_exported() luaunit.assertEquals(GestureRecognizer.GestureState.POSSIBLE, "possible") luaunit.assertEquals(GestureRecognizer.GestureState.BEGAN, "began") luaunit.assertEquals(GestureRecognizer.GestureState.CHANGED, "changed") luaunit.assertEquals(GestureRecognizer.GestureState.ENDED, "ended") luaunit.assertEquals(GestureRecognizer.GestureState.CANCELLED, "cancelled") luaunit.assertEquals(GestureRecognizer.GestureState.FAILED, "failed") end -- ============================================================================ -- Touch Scroll: Press Tests -- ============================================================================ TestTouchScrollPress = {} function TestTouchScrollPress:setUp() love.timer.setTime(0) end function TestTouchScrollPress:test_handleTouchPress_starts_scrolling() local sm = createTouchScrollManager() local el = createMockElement() sm:detectOverflow(el) local started = sm:handleTouchPress(100, 150) luaunit.assertTrue(started) luaunit.assertTrue(sm:isTouchScrolling()) end function TestTouchScrollPress:test_handleTouchPress_disabled_returns_false() local sm = createTouchScrollManager({ touchScrollEnabled = false }) local el = createMockElement() sm:detectOverflow(el) local started = sm:handleTouchPress(100, 150) luaunit.assertFalse(started) luaunit.assertFalse(sm:isTouchScrolling()) end function TestTouchScrollPress:test_handleTouchPress_no_overflow_returns_false() local sm = createTouchScrollManager({ overflow = "hidden" }) local started = sm:handleTouchPress(100, 150) luaunit.assertFalse(started) end function TestTouchScrollPress:test_handleTouchPress_stops_momentum_scrolling() local sm = createTouchScrollManager() local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) sm._momentumScrolling = true sm._scrollVelocityY = 500 sm:handleTouchPress(100, 200) luaunit.assertFalse(sm:isMomentumScrolling()) luaunit.assertEquals(sm._scrollVelocityX, 0) luaunit.assertEquals(sm._scrollVelocityY, 0) end -- ============================================================================ -- Touch Scroll: Move Tests -- ============================================================================ TestTouchScrollMove = {} function TestTouchScrollMove:setUp() love.timer.setTime(0) end function TestTouchScrollMove:test_handleTouchMove_scrolls_content() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) love.timer.step(1 / 60) local handled = sm:handleTouchMove(100, 150) luaunit.assertTrue(handled) luaunit.assertTrue(sm._scrollY > 0) end function TestTouchScrollMove:test_handleTouchMove_without_press_returns_false() local sm = createTouchScrollManager() local handled = sm:handleTouchMove(100, 150) luaunit.assertFalse(handled) end function TestTouchScrollMove:test_handleTouchMove_calculates_velocity() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) love.timer.step(1 / 60) sm:handleTouchMove(100, 100) luaunit.assertTrue(sm._scrollVelocityY > 0) end function TestTouchScrollMove:test_handleTouchMove_horizontal() local sm = createTouchScrollManager({ bounceEnabled = false, overflowX = "scroll", overflowY = "hidden", }) local el = createMockElement(200, 300, 600, 300) sm:detectOverflow(el) sm:handleTouchPress(200, 150) love.timer.step(1 / 60) sm:handleTouchMove(100, 150) luaunit.assertTrue(sm._scrollX > 0) end function TestTouchScrollMove:test_handleTouchMove_with_bounce_allows_overscroll() local sm = createTouchScrollManager({ bounceEnabled = true, maxOverscroll = 100 }) local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 100) love.timer.step(1 / 60) sm:handleTouchMove(100, 200) luaunit.assertTrue(sm._scrollY < 0) end -- ============================================================================ -- Touch Scroll: Release and Momentum Tests -- ============================================================================ TestTouchScrollRelease = {} function TestTouchScrollRelease:setUp() love.timer.setTime(0) end function TestTouchScrollRelease:test_handleTouchRelease_ends_touch_scrolling() local sm = createTouchScrollManager() local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) luaunit.assertTrue(sm:isTouchScrolling()) sm:handleTouchRelease() luaunit.assertFalse(sm:isTouchScrolling()) end function TestTouchScrollRelease:test_handleTouchRelease_without_press_returns_false() local sm = createTouchScrollManager() local released = sm:handleTouchRelease() luaunit.assertFalse(released) end function TestTouchScrollRelease:test_handleTouchRelease_starts_momentum_with_velocity() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) love.timer.step(1 / 60) sm:handleTouchMove(100, 50) sm:handleTouchRelease() luaunit.assertTrue(sm:isMomentumScrolling()) end function TestTouchScrollRelease:test_handleTouchRelease_no_momentum_with_low_velocity() local sm = createTouchScrollManager() local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) sm._scrollVelocityX = 0 sm._scrollVelocityY = 10 sm:handleTouchRelease() luaunit.assertFalse(sm:isMomentumScrolling()) end function TestTouchScrollRelease:test_handleTouchRelease_no_momentum_when_disabled() local sm = createTouchScrollManager({ momentumScrollEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) sm._scrollVelocityY = 500 sm:handleTouchRelease() luaunit.assertFalse(sm:isMomentumScrolling()) luaunit.assertEquals(sm._scrollVelocityX, 0) luaunit.assertEquals(sm._scrollVelocityY, 0) end -- ============================================================================ -- Touch Scroll: Momentum Tests -- ============================================================================ TestTouchScrollMomentum = {} function TestTouchScrollMomentum:setUp() love.timer.setTime(0) end function TestTouchScrollMomentum:test_momentum_decelerates_over_time() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm._momentumScrolling = true sm._scrollVelocityY = 200 local initialVelocity = sm._scrollVelocityY sm:update(1 / 60) luaunit.assertTrue(sm._scrollVelocityY < initialVelocity) luaunit.assertTrue(sm._scrollVelocityY > 0) end function TestTouchScrollMomentum:test_momentum_stops_at_low_velocity() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm._momentumScrolling = true sm._scrollVelocityY = 200 for i = 1, 500 do sm:update(1 / 60) if not sm:isMomentumScrolling() then break end end luaunit.assertFalse(sm:isMomentumScrolling()) luaunit.assertEquals(sm._scrollVelocityY, 0) end function TestTouchScrollMomentum:test_momentum_moves_scroll_position() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm._momentumScrolling = true sm._scrollVelocityY = 500 local initialScrollY = sm._scrollY sm:update(1 / 60) luaunit.assertTrue(sm._scrollY > initialScrollY) end function TestTouchScrollMomentum:test_friction_coefficient_affects_deceleration() local smFast = createTouchScrollManager({ scrollFriction = 0.99, bounceEnabled = false }) local smSlow = createTouchScrollManager({ scrollFriction = 0.90, bounceEnabled = false }) local el = createMockElement() smFast:detectOverflow(el) smSlow:detectOverflow(el) smFast._momentumScrolling = true smFast._scrollVelocityY = 200 smSlow._momentumScrolling = true smSlow._scrollVelocityY = 200 smFast:update(1 / 60) smSlow:update(1 / 60) luaunit.assertTrue(smFast._scrollVelocityY > smSlow._scrollVelocityY) end -- ============================================================================ -- Touch Scroll: Bounce Tests -- ============================================================================ TestTouchScrollBounce = {} function TestTouchScrollBounce:setUp() love.timer.setTime(0) end function TestTouchScrollBounce:test_bounce_returns_to_boundary() local sm = createTouchScrollManager({ bounceEnabled = true }) local el = createMockElement() sm:detectOverflow(el) sm._scrollY = -50 for i = 1, 100 do sm:update(1 / 60) end luaunit.assertAlmostEquals(sm._scrollY, 0, 1) end function TestTouchScrollBounce:test_bounce_at_bottom_boundary() local sm = createTouchScrollManager({ bounceEnabled = true }) local el = createMockElement() sm:detectOverflow(el) sm._scrollY = sm._maxScrollY + 50 for i = 1, 100 do sm:update(1 / 60) end luaunit.assertAlmostEquals(sm._scrollY, sm._maxScrollY, 1) end function TestTouchScrollBounce:test_no_bounce_when_disabled() local sm = createTouchScrollManager({ bounceEnabled = false }) local el = createMockElement() sm:detectOverflow(el) sm._scrollY = -50 sm:update(1 / 60) luaunit.assertEquals(sm._scrollY, -50) end -- ============================================================================ -- Touch Scroll: State Query Tests -- ============================================================================ TestTouchScrollState = {} function TestTouchScrollState:setUp() love.timer.setTime(0) end function TestTouchScrollState:test_isTouchScrolling_initially_false() local sm = createTouchScrollManager() luaunit.assertFalse(sm:isTouchScrolling()) end function TestTouchScrollState:test_isMomentumScrolling_initially_false() local sm = createTouchScrollManager() luaunit.assertFalse(sm:isMomentumScrolling()) end function TestTouchScrollState:test_isTouchScrolling_true_during_touch() local sm = createTouchScrollManager() local el = createMockElement() sm:detectOverflow(el) sm:handleTouchPress(100, 200) luaunit.assertTrue(sm:isTouchScrolling()) sm:handleTouchRelease() luaunit.assertFalse(sm:isTouchScrolling()) end -- ============================================================================ -- Touch Scroll: Configuration Tests -- ============================================================================ TestTouchScrollConfig = {} function TestTouchScrollConfig:setUp() love.timer.setTime(0) end function TestTouchScrollConfig:test_default_config_values() local sm = createTouchScrollManager() luaunit.assertTrue(sm.touchScrollEnabled) luaunit.assertTrue(sm.momentumScrollEnabled) luaunit.assertTrue(sm.bounceEnabled) luaunit.assertEquals(sm.scrollFriction, 0.95) luaunit.assertEquals(sm.bounceStiffness, 0.2) luaunit.assertEquals(sm.maxOverscroll, 100) end function TestTouchScrollConfig:test_custom_config_values() local sm = createTouchScrollManager({ touchScrollEnabled = false, momentumScrollEnabled = false, bounceEnabled = false, scrollFriction = 0.98, bounceStiffness = 0.1, maxOverscroll = 50, }) luaunit.assertFalse(sm.touchScrollEnabled) luaunit.assertFalse(sm.momentumScrollEnabled) luaunit.assertFalse(sm.bounceEnabled) luaunit.assertEquals(sm.scrollFriction, 0.98) luaunit.assertEquals(sm.bounceStiffness, 0.1) luaunit.assertEquals(sm.maxOverscroll, 50) end -- ============================================================================ -- Touch Routing Tests -- ============================================================================ TestTouchRouting = {} function TestTouchRouting:setUp() FlexLove.setMode("immediate") love.window.setMode(800, 600) end function TestTouchRouting:tearDown() FlexLove.destroy() love.touch.getTouches = function() return {} end love.touch.getPosition = function() return 0, 0 end end function TestTouchRouting:test_touchpressed_routes_to_element() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertTrue(#touchEvents >= 1, "Should receive touchpress event") luaunit.assertEquals(touchEvents[1].type, "touchpress") luaunit.assertEquals(touchEvents[1].touchId, "touch1") luaunit.assertEquals(touchEvents[1].x, 100) luaunit.assertEquals(touchEvents[1].y, 100) end function TestTouchRouting:test_touchmoved_routes_to_owner() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) FlexLove.touchmoved("touch1", 150, 150, 50, 50, 1.0) local moveEvents = {} for _, e in ipairs(touchEvents) do if e.type == "touchmove" then table.insert(moveEvents, e) end end luaunit.assertTrue(#moveEvents >= 1, "Should receive touchmove event") luaunit.assertEquals(moveEvents[1].x, 150) luaunit.assertEquals(moveEvents[1].y, 150) end function TestTouchRouting:test_touchreleased_routes_and_cleans_up() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertNotNil(FlexLove.getTouchOwner("touch1"), "Touch should be owned") FlexLove.touchreleased("touch1", 100, 100, 0, 0, 1.0) luaunit.assertNil(FlexLove.getTouchOwner("touch1"), "Touch ownership should be cleaned up") local releaseEvents = {} for _, e in ipairs(touchEvents) do if e.type == "touchrelease" then table.insert(releaseEvents, e) end end luaunit.assertTrue(#releaseEvents >= 1, "Should receive touchrelease event") end function TestTouchRouting:test_touch_ownership_persists_outside_bounds() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) FlexLove.touchmoved("touch1", 500, 500, 400, 400, 1.0) local moveEvents = {} for _, e in ipairs(touchEvents) do if e.type == "touchmove" then table.insert(moveEvents, e) end end luaunit.assertTrue(#moveEvents >= 1, "Move event should route to owner even outside bounds") luaunit.assertEquals(moveEvents[1].x, 500) luaunit.assertEquals(moveEvents[1].y, 500) end function TestTouchRouting:test_touch_outside_elements_no_ownership() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 100, height = 100, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 500, 500, 0, 0, 1.0) luaunit.assertNil(FlexLove.getTouchOwner("touch1"), "No element should own touch outside bounds") luaunit.assertEquals(#touchEvents, 0, "No events should fire for touch outside bounds") end function TestTouchRouting:test_multi_touch_different_elements() FlexLove.beginFrame() local events1 = {} local events2 = {} local container = FlexLove.new({ width = 400, height = 200, }) local element1 = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(events1, event) end, parent = container, }) local element2 = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(events2, event) end, parent = container, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 50, 100, 0, 0, 1.0) FlexLove.touchpressed("touch2", 300, 100, 0, 0, 1.0) luaunit.assertTrue(#events1 >= 1, "Element1 should receive touch event") luaunit.assertTrue(#events2 >= 1, "Element2 should receive touch event") luaunit.assertEquals(events1[1].touchId, "touch1") luaunit.assertEquals(events2[1].touchId, "touch2") end function TestTouchRouting:test_z_index_ordering() FlexLove.beginFrame() local eventsLow = {} local eventsHigh = {} local low = FlexLove.new({ width = 200, height = 200, z = 1, onTouchEvent = function(el, event) table.insert(eventsLow, event) end, }) local high = FlexLove.new({ width = 200, height = 200, z = 10, onTouchEvent = function(el, event) table.insert(eventsHigh, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertTrue(#eventsHigh >= 1, "Higher z element should receive touch") luaunit.assertEquals(#eventsLow, 0, "Lower z element should NOT receive touch") end function TestTouchRouting:test_disabled_element_no_touch() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, disabled = true, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertEquals(#touchEvents, 0, "Disabled element should not receive touch events") luaunit.assertNil(FlexLove.getTouchOwner("touch1")) end function TestTouchRouting:test_getActiveTouchCount() FlexLove.beginFrame() local element = FlexLove.new({ width = 800, height = 600, onTouchEvent = function() end, }) FlexLove.endFrame() luaunit.assertEquals(FlexLove.getActiveTouchCount(), 0) FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertEquals(FlexLove.getActiveTouchCount(), 1) FlexLove.touchpressed("touch2", 200, 200, 0, 0, 1.0) luaunit.assertEquals(FlexLove.getActiveTouchCount(), 2) FlexLove.touchreleased("touch1", 100, 100, 0, 0, 1.0) luaunit.assertEquals(FlexLove.getActiveTouchCount(), 1) FlexLove.touchreleased("touch2", 200, 200, 0, 0, 1.0) luaunit.assertEquals(FlexLove.getActiveTouchCount(), 0) end function TestTouchRouting:test_getTouchOwner() FlexLove.beginFrame() local element = FlexLove.new({ id = "owner-test", width = 200, height = 200, onTouchEvent = function() end, }) FlexLove.endFrame() luaunit.assertNil(FlexLove.getTouchOwner("touch1")) FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) local owner = FlexLove.getTouchOwner("touch1") luaunit.assertNotNil(owner) luaunit.assertEquals(owner.id, "owner-test") end function TestTouchRouting:test_destroy_cleans_touch_state() FlexLove.beginFrame() local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function() end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertEquals(FlexLove.getActiveTouchCount(), 1) FlexLove.destroy() luaunit.assertEquals(FlexLove.getActiveTouchCount(), 0) end function TestTouchRouting:test_onEvent_receives_touch_events() FlexLove.beginFrame() local allEvents = {} local element = FlexLove.new({ width = 200, height = 200, onEvent = function(el, event) table.insert(allEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) local touchPressEvents = {} for _, e in ipairs(allEvents) do if e.type == "touchpress" then table.insert(touchPressEvents, e) end end luaunit.assertTrue(#touchPressEvents >= 1, "onEvent should receive touchpress events") end function TestTouchRouting:test_gesture_routing() FlexLove.beginFrame() local gestureEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function() end, onGesture = function(el, gesture) table.insert(gestureEvents, gesture) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) love.timer.step(0.05) FlexLove.touchreleased("touch1", 100, 100, 0, 0, 1.0) local tapGestures = {} for _, g in ipairs(gestureEvents) do if g.type == "tap" then table.insert(tapGestures, g) end end luaunit.assertTrue(#tapGestures >= 1, "Should detect tap gesture from press+release") end function TestTouchRouting:test_element_with_only_onGesture() FlexLove.beginFrame() local gestureEvents = {} local element = FlexLove.new({ width = 200, height = 200, onGesture = function(el, gesture) table.insert(gestureEvents, gesture) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertNotNil(FlexLove.getTouchOwner("touch1"), "Element with onGesture should be found") end function TestTouchRouting:test_touchEnabled_false_prevents_routing() FlexLove.beginFrame() local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, touchEnabled = false, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertNil(FlexLove.getTouchOwner("touch1"), "touchEnabled=false should prevent ownership") luaunit.assertEquals(#touchEvents, 0, "touchEnabled=false should prevent events") end function TestTouchRouting:test_full_lifecycle() FlexLove.beginFrame() local phases = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(phases, event.type) end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) FlexLove.touchmoved("touch1", 110, 110, 10, 10, 1.0) FlexLove.touchmoved("touch1", 120, 120, 10, 10, 1.0) FlexLove.touchreleased("touch1", 120, 120, 0, 0, 1.0) luaunit.assertEquals(phases[1], "touchpress") luaunit.assertEquals(phases[2], "touchmove") luaunit.assertEquals(phases[3], "touchmove") luaunit.assertEquals(phases[4], "touchrelease") luaunit.assertEquals(#phases, 4) end function TestTouchRouting:test_orphaned_move_release_no_crash() FlexLove.touchmoved("ghost_touch", 100, 100, 0, 0, 1.0) FlexLove.touchreleased("ghost_touch", 100, 100, 0, 0, 1.0) luaunit.assertEquals(FlexLove.getActiveTouchCount(), 0) end function TestTouchRouting:test_pressure_passthrough() FlexLove.beginFrame() local receivedPressure = nil local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) if event.type == "touchpress" then receivedPressure = event.pressure end end, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 0.75) luaunit.assertAlmostEquals(receivedPressure, 0.75, 0.01) end function TestTouchRouting:test_retained_mode_routing() FlexLove.setMode("retained") local touchEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, }) FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertTrue(#touchEvents >= 1, "Touch routing should work in retained mode") luaunit.assertEquals(touchEvents[1].type, "touchpress") end function TestTouchRouting:test_child_receives_touch_over_parent() FlexLove.beginFrame() local parentEvents = {} local childEvents = {} local parent = FlexLove.new({ width = 400, height = 400, onTouchEvent = function(el, event) table.insert(parentEvents, event) end, }) local child = FlexLove.new({ width = 200, height = 200, z = 1, onTouchEvent = function(el, event) table.insert(childEvents, event) end, parent = parent, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertTrue(#childEvents >= 1, string.format("Child should receive touch (child=%d, parent=%d, topElements=%d)", #childEvents, #parentEvents, #FlexLove.topElements)) end function TestTouchRouting:test_non_interactive_element_ignored() FlexLove.beginFrame() local element = FlexLove.new({ width = 200, height = 200, }) FlexLove.endFrame() FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) luaunit.assertNil(FlexLove.getTouchOwner("touch1"), "Non-interactive element should not capture touch") end -- ============================================================================ -- Element Touch Property Tests -- ============================================================================ TestTouchElementProps = {} function TestTouchElementProps:setUp() FlexLove.setMode("immediate") love.window.setMode(800, 600) end function TestTouchElementProps:tearDown() FlexLove.destroy() end function TestTouchElementProps:test_touchEnabled_defaults_true() FlexLove.beginFrame() local element = FlexLove.new({ width = 100, height = 100 }) FlexLove.endFrame() luaunit.assertTrue(element.touchEnabled) end function TestTouchElementProps:test_touchEnabled_can_be_set_false() FlexLove.beginFrame() local element = FlexLove.new({ width = 100, height = 100, touchEnabled = false }) FlexLove.endFrame() luaunit.assertFalse(element.touchEnabled) end function TestTouchElementProps:test_multiTouchEnabled_defaults_false() FlexLove.beginFrame() local element = FlexLove.new({ width = 100, height = 100 }) FlexLove.endFrame() luaunit.assertFalse(element.multiTouchEnabled) end function TestTouchElementProps:test_multiTouchEnabled_can_be_set_true() FlexLove.beginFrame() local element = FlexLove.new({ width = 100, height = 100, multiTouchEnabled = true }) FlexLove.endFrame() luaunit.assertTrue(element.multiTouchEnabled) end -- ============================================================================ -- Element Touch Callback Tests -- ============================================================================ TestTouchElementCallbacks = {} function TestTouchElementCallbacks:setUp() FlexLove.setMode("immediate") love.window.setMode(800, 600) end function TestTouchElementCallbacks:tearDown() FlexLove.destroy() end function TestTouchElementCallbacks:test_onTouchEvent_callback() FlexLove.beginFrame() local receivedEvents = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function(el, event) table.insert(receivedEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("t1", 100, 100, 0, 0, 1.0) luaunit.assertTrue(#receivedEvents >= 1) luaunit.assertEquals(receivedEvents[1].type, "touchpress") end function TestTouchElementCallbacks:test_onGesture_callback() FlexLove.beginFrame() local receivedGestures = {} local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function() end, onGesture = function(el, gesture) table.insert(receivedGestures, gesture) end, }) FlexLove.endFrame() FlexLove.touchpressed("t1", 100, 100, 0, 0, 1.0) love.timer.step(0.05) FlexLove.touchreleased("t1", 100, 100, 0, 0, 1.0) local tapGestures = {} for _, g in ipairs(receivedGestures) do if g.type == "tap" then table.insert(tapGestures, g) end end luaunit.assertTrue(#tapGestures >= 1, "Should receive tap gesture callback") end function TestTouchElementCallbacks:test_onEvent_also_receives_touch() FlexLove.beginFrame() local receivedEvents = {} local element = FlexLove.new({ width = 200, height = 200, onEvent = function(el, event) table.insert(receivedEvents, event) end, }) FlexLove.endFrame() FlexLove.touchpressed("t1", 100, 100, 0, 0, 1.0) local touchEvents = {} for _, e in ipairs(receivedEvents) do if e.type == "touchpress" then table.insert(touchEvents, e) end end luaunit.assertTrue(#touchEvents >= 1, "onEvent should receive touch events") end -- ============================================================================ -- Element handleTouchEvent Direct Tests -- ============================================================================ TestTouchElementDirect = {} function TestTouchElementDirect:setUp() FlexLove.setMode("immediate") love.window.setMode(800, 600) end function TestTouchElementDirect:tearDown() FlexLove.destroy() end function TestTouchElementDirect:test_handleTouchEvent_disabled_element() FlexLove.beginFrame() local receivedEvents = {} local element = FlexLove.new({ width = 200, height = 200, disabled = true, onTouchEvent = function(el, event) table.insert(receivedEvents, event) end, }) FlexLove.endFrame() local touchEvt = InputEvent.fromTouch("t1", 100, 100, "began", 1.0) element:handleTouchEvent(touchEvt) luaunit.assertEquals(#receivedEvents, 0, "Disabled element should not receive touch events") end function TestTouchElementDirect:test_handleTouchEvent_touchEnabled_false() FlexLove.beginFrame() local receivedEvents = {} local element = FlexLove.new({ width = 200, height = 200, touchEnabled = false, onTouchEvent = function(el, event) table.insert(receivedEvents, event) end, }) FlexLove.endFrame() local touchEvt = InputEvent.fromTouch("t1", 100, 100, "began", 1.0) element:handleTouchEvent(touchEvt) luaunit.assertEquals(#receivedEvents, 0, "touchEnabled=false should prevent events") end function TestTouchElementDirect:test_handleGesture_fires_callback() FlexLove.beginFrame() local receivedGestures = {} local element = FlexLove.new({ width = 200, height = 200, onGesture = function(el, gesture) table.insert(receivedGestures, gesture) end, }) FlexLove.endFrame() element:handleGesture({ type = "tap", state = "ended", x = 100, y = 100 }) luaunit.assertEquals(#receivedGestures, 1) luaunit.assertEquals(receivedGestures[1].type, "tap") end function TestTouchElementDirect:test_handleGesture_disabled_element() FlexLove.beginFrame() local receivedGestures = {} local element = FlexLove.new({ width = 200, height = 200, disabled = true, onGesture = function(el, gesture) table.insert(receivedGestures, gesture) end, }) FlexLove.endFrame() element:handleGesture({ type = "tap", state = "ended", x = 100, y = 100 }) luaunit.assertEquals(#receivedGestures, 0, "Disabled element should not receive gestures") end function TestTouchElementDirect:test_handleGesture_touchEnabled_false() FlexLove.beginFrame() local receivedGestures = {} local element = FlexLove.new({ width = 200, height = 200, touchEnabled = false, onGesture = function(el, gesture) table.insert(receivedGestures, gesture) end, }) FlexLove.endFrame() element:handleGesture({ type = "tap", state = "ended", x = 100, y = 100 }) luaunit.assertEquals(#receivedGestures, 0, "touchEnabled=false should prevent gestures") end function TestTouchElementDirect:test_getTouches_returns_table() FlexLove.beginFrame() local element = FlexLove.new({ width = 200, height = 200, onTouchEvent = function() end, }) FlexLove.endFrame() local touches = element:getTouches() luaunit.assertEquals(type(touches), "table") end function TestTouchElementDirect:test_touch_pan_lifecycle() FlexLove.beginFrame() local touchEvents = {} local gestureEvents = {} local element = FlexLove.new({ width = 400, height = 400, onTouchEvent = function(el, event) table.insert(touchEvents, event) end, onGesture = function(el, gesture) table.insert(gestureEvents, gesture) end, }) FlexLove.endFrame() FlexLove.touchpressed("t1", 100, 100, 0, 0, 1.0) love.timer.step(0.05) FlexLove.touchmoved("t1", 150, 150, 50, 50, 1.0) love.timer.step(0.05) FlexLove.touchmoved("t1", 200, 200, 50, 50, 1.0) love.timer.step(0.05) FlexLove.touchreleased("t1", 200, 200, 0, 0, 1.0) luaunit.assertTrue(#touchEvents >= 3, "Should receive press + move + release touch events") local panGestures = {} for _, g in ipairs(gestureEvents) do if g.type == "pan" then table.insert(panGestures, g) end end luaunit.assertTrue(#panGestures >= 1, "Should receive pan gesture events") end function TestTouchElementDirect:test_onTouchEventDeferred_prop_accepted() FlexLove.beginFrame() local element = FlexLove.new({ width = 200, height = 200, onTouchEventDeferred = function() end, }) FlexLove.endFrame() luaunit.assertNotNil(element, "Element with onTouchEventDeferred should be created") end function TestTouchElementDirect:test_onGestureDeferred_prop_accepted() FlexLove.beginFrame() local element = FlexLove.new({ width = 200, height = 200, onGestureDeferred = function() end, }) FlexLove.endFrame() luaunit.assertNotNil(element, "Element with onGestureDeferred should be created") end if not _G.RUNNING_ALL_TESTS then os.exit(luaunit.LuaUnit.run()) end