From ffab292c04237d6578a6e002bd3301a0ebe6ace6 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Wed, 25 Feb 2026 11:07:46 -0500 Subject: [PATCH] housekeeping: flatten tests --- testing/__tests__/element_touch_test.lua | 328 --- testing/__tests__/gesture_recognizer_test.lua | 533 ----- testing/__tests__/touch_events_test.lua | 303 --- testing/__tests__/touch_routing_test.lua | 528 ----- testing/__tests__/touch_scroll_test.lua | 475 ---- testing/__tests__/touch_test.lua | 1902 +++++++++++++++++ testing/runAll.lua | 6 +- 7 files changed, 1903 insertions(+), 2172 deletions(-) delete mode 100644 testing/__tests__/element_touch_test.lua delete mode 100644 testing/__tests__/gesture_recognizer_test.lua delete mode 100644 testing/__tests__/touch_events_test.lua delete mode 100644 testing/__tests__/touch_routing_test.lua delete mode 100644 testing/__tests__/touch_scroll_test.lua create mode 100644 testing/__tests__/touch_test.lua diff --git a/testing/__tests__/element_touch_test.lua b/testing/__tests__/element_touch_test.lua deleted file mode 100644 index 1ee896b..0000000 --- a/testing/__tests__/element_touch_test.lua +++ /dev/null @@ -1,328 +0,0 @@ -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") - -FlexLove.init() - -TestElementTouch = {} - -function TestElementTouch:setUp() - FlexLove.setMode("immediate") - love.window.setMode(800, 600) -end - -function TestElementTouch:tearDown() - FlexLove.destroy() -end - --- ============================================ --- Touch Property Tests --- ============================================ - -function TestElementTouch:test_touchEnabled_defaults_true() - FlexLove.beginFrame() - local element = FlexLove.new({ width = 100, height = 100 }) - FlexLove.endFrame() - - luaunit.assertTrue(element.touchEnabled) -end - -function TestElementTouch: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 TestElementTouch:test_multiTouchEnabled_defaults_false() - FlexLove.beginFrame() - local element = FlexLove.new({ width = 100, height = 100 }) - FlexLove.endFrame() - - luaunit.assertFalse(element.multiTouchEnabled) -end - -function TestElementTouch: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 - --- ============================================ --- Touch Callback Tests --- ============================================ - -function TestElementTouch: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 TestElementTouch: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() - - -- Quick tap - 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 TestElementTouch: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 - --- ============================================ --- handleTouchEvent direct tests --- ============================================ - -function TestElementTouch: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 InputEvent = package.loaded["modules.InputEvent"] - local touchEvent = InputEvent.fromTouch("t1", 100, 100, "began", 1.0) - element:handleTouchEvent(touchEvent) - - luaunit.assertEquals(#receivedEvents, 0, "Disabled element should not receive touch events") -end - -function TestElementTouch: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 InputEvent = package.loaded["modules.InputEvent"] - local touchEvent = InputEvent.fromTouch("t1", 100, 100, "began", 1.0) - element:handleTouchEvent(touchEvent) - - luaunit.assertEquals(#receivedEvents, 0, "touchEnabled=false should prevent events") -end - --- ============================================ --- handleGesture direct tests --- ============================================ - -function TestElementTouch: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 TestElementTouch: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 TestElementTouch: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 - --- ============================================ --- getTouches tests --- ============================================ - -function TestElementTouch: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 - --- ============================================ --- Touch + Gesture combined lifecycle --- ============================================ - -function TestElementTouch: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() - - -- Simulate a pan gesture: press, move significantly, release - 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) - - -- Should have received touch events - luaunit.assertTrue(#touchEvents >= 3, "Should receive press + move + release touch events") - - -- Should have received pan gestures from GestureRecognizer - 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 - --- ============================================ --- Deferred callbacks --- ============================================ - -function TestElementTouch:test_onTouchEventDeferred_prop_accepted() - FlexLove.beginFrame() - -- Just test that the prop is accepted without error - local element = FlexLove.new({ - width = 200, - height = 200, - onTouchEventDeferred = function() end, - }) - FlexLove.endFrame() - - luaunit.assertNotNil(element, "Element with onTouchEventDeferred should be created") -end - -function TestElementTouch: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 diff --git a/testing/__tests__/gesture_recognizer_test.lua b/testing/__tests__/gesture_recognizer_test.lua deleted file mode 100644 index dd1fbe2..0000000 --- a/testing/__tests__/gesture_recognizer_test.lua +++ /dev/null @@ -1,533 +0,0 @@ -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") - -FlexLove.init() - -local InputEvent = package.loaded["modules.InputEvent"] -local GestureRecognizer = package.loaded["modules.GestureRecognizer"] - -TestGestureRecognizer = {} - -function TestGestureRecognizer:setUp() - self.recognizer = GestureRecognizer.new({}, { InputEvent = InputEvent, utils = {} }) - love.timer.setTime(0) -end - -function TestGestureRecognizer:tearDown() - self.recognizer:reset() -end - --- Helper: create touch event -local function touchEvent(id, x, y, phase, time) - if time then love.timer.setTime(time) end - local event = InputEvent.fromTouch(id, x, y, phase, 1.0) - return event -end - --- ============================================ --- Tap Gesture Tests --- ============================================ - -function TestGestureRecognizer: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 TestGestureRecognizer:test_tap_not_detected_when_too_slow() - local event1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(event1) - - -- Release after tapMaxDuration (0.3s) - local event2 = touchEvent("t1", 100, 100, "ended", 0.5) - local gestures = self.recognizer:processTouchEvent(event2) - - -- Should not be a tap (too slow) - if gestures then - for _, g in ipairs(gestures) do - luaunit.assertNotEquals(g.type, "tap", "Slow touch should not be tap") - end - end -end - -function TestGestureRecognizer:test_tap_not_detected_when_moved_too_far() - local event1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(event1) - - -- Move more than tapMaxMovement (10px) - 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) - - -- Should not be a tap (moved too far) - if gestures then - for _, g in ipairs(gestures) do - if g.type == "tap" then - -- Tap detection checks distance from START to END position - 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 TestGestureRecognizer:test_double_tap_detected() - -- First tap - 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) - - -- Second tap quickly - 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") - -- Should have double_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 TestGestureRecognizer:test_double_tap_not_detected_when_too_slow() - -- First tap - 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) - - -- Second tap too late (>0.3s interval) - 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) - - -- Should detect tap but NOT double_tap - 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 Gesture Tests --- ============================================ - -function TestGestureRecognizer:test_pan_began() - local e1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(e1) - - -- Move beyond panMinMovement (5px) - 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 TestGestureRecognizer:test_pan_changed() - local e1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(e1) - - -- First move to start pan - local e2 = touchEvent("t1", 110, 110, "moved", 0.05) - self.recognizer:processTouchEvent(e2) - - -- Continue moving - 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) -- delta from startX=100 (lastX set to startX on began) - luaunit.assertEquals(panChanged.dy, 20) -end - -function TestGestureRecognizer: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 TestGestureRecognizer:test_pan_not_detected_with_small_movement() - local e1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(e1) - - -- Move less than panMinMovement (5px) - 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 Gesture Tests --- ============================================ - -function TestGestureRecognizer:test_swipe_right() - local e1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(e1) - - -- Fast swipe right (>50px in <0.2s with >200px/s velocity) - 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 TestGestureRecognizer: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 TestGestureRecognizer: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 TestGestureRecognizer: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 TestGestureRecognizer:test_swipe_not_detected_when_too_slow() - local e1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(e1) - - -- Too slow (>0.2s) - 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 TestGestureRecognizer:test_swipe_not_detected_when_too_short() - local e1 = touchEvent("t1", 100, 100, "began", 0) - self.recognizer:processTouchEvent(e1) - - -- Too short distance (<50px) - 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 Gesture Tests --- ============================================ - -function TestGestureRecognizer:test_pinch_detected() - -- Two fingers start 100px apart - local e1 = touchEvent("t1", 100, 200, "began", 0) - self.recognizer:processTouchEvent(e1) - local e2 = touchEvent("t2", 200, 200, "began", 0) - self.recognizer:processTouchEvent(e2) - - -- Move fingers apart to 200px (scale = 2.0) - local e3 = touchEvent("t1", 50, 200, "moved", 0.1) - local gestures = self.recognizer:processTouchEvent(e3) - local e4 = touchEvent("t2", 250, 200, "moved", 0.1) - 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 TestGestureRecognizer:test_pinch_scale_decreases() - -- Two fingers start 200px apart - local e1 = touchEvent("t1", 50, 200, "began", 0) - self.recognizer:processTouchEvent(e1) - local e2 = touchEvent("t2", 250, 200, "began", 0) - self.recognizer:processTouchEvent(e2) - - -- Move fingers closer to 100px (scale = 0.5) - 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 TestGestureRecognizer: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 Gesture Tests --- ============================================ - -function TestGestureRecognizer:test_rotate_detected() - -- Two fingers horizontally - local e1 = touchEvent("t1", 100, 200, "began", 0) - self.recognizer:processTouchEvent(e1) - local e2 = touchEvent("t2", 200, 200, "began", 0) - self.recognizer:processTouchEvent(e2) - - -- Rotate: move t2 above t1 (significant angle change > 5 degrees) - 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 - --- ============================================ --- processTouchEvent return value tests --- ============================================ - -function TestGestureRecognizer:test_processTouchEvent_returns_nil_for_no_gesture() - local e1 = touchEvent("t1", 100, 100, "began", 0) - local gestures = self.recognizer:processTouchEvent(e1) - - -- Press alone should not produce gesture - luaunit.assertNil(gestures, "Press alone should not produce gesture") -end - -function TestGestureRecognizer: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 TestGestureRecognizer:test_processTouchEvent_ignores_no_touchId() - local event = { type = "touchpress", x = 100, y = 100 } -- No touchId - local gestures = self.recognizer:processTouchEvent(event) - luaunit.assertNil(gestures) -end - -function TestGestureRecognizer: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) - local gestures = self.recognizer:processTouchEvent(e2) - - -- After cancel, no touches should remain - luaunit.assertEquals(self.recognizer:_getTouchCount(), 0) -end - --- ============================================ --- Reset Tests --- ============================================ - -function TestGestureRecognizer: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 - --- ============================================ --- Custom Configuration Tests --- ============================================ - -function TestGestureRecognizer: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) - -- Defaults for non-overridden values - luaunit.assertEquals(custom._config.swipeMinDistance, 50) -end - --- ============================================ --- GestureType and GestureState exports --- ============================================ - -function TestGestureRecognizer: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 TestGestureRecognizer: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 - -if not _G.RUNNING_ALL_TESTS then - os.exit(luaunit.LuaUnit.run()) -end diff --git a/testing/__tests__/touch_events_test.lua b/testing/__tests__/touch_events_test.lua deleted file mode 100644 index 6786f7d..0000000 --- a/testing/__tests__/touch_events_test.lua +++ /dev/null @@ -1,303 +0,0 @@ -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") - --- Initialize FlexLove to ensure all modules are properly set up -FlexLove.init() - -TestTouchEvents = {} - --- Test: InputEvent.fromTouch creates valid touch event -function TestTouchEvents:testInputEvent_FromTouch() - local InputEvent = package.loaded["modules.InputEvent"] - - local touchId = "touch1" - local event = InputEvent.fromTouch(touchId, 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) -- Treat as left button -end - --- Test: Touch event with moved phase -function TestTouchEvents:testInputEvent_FromTouch_Moved() - local InputEvent = package.loaded["modules.InputEvent"] - - local event = InputEvent.fromTouch("touch1", 150, 250, "moved", 1.0) - - luaunit.assertEquals(event.type, "touchmove") - luaunit.assertEquals(event.phase, "moved") -end - --- Test: Touch event with ended phase -function TestTouchEvents:testInputEvent_FromTouch_Ended() - local InputEvent = package.loaded["modules.InputEvent"] - - local event = InputEvent.fromTouch("touch1", 150, 250, "ended", 1.0) - - luaunit.assertEquals(event.type, "touchrelease") - luaunit.assertEquals(event.phase, "ended") -end - --- Test: Touch event with cancelled phase -function TestTouchEvents:testInputEvent_FromTouch_Cancelled() - local InputEvent = package.loaded["modules.InputEvent"] - - local event = InputEvent.fromTouch("touch1", 150, 250, "cancelled", 1.0) - - luaunit.assertEquals(event.type, "touchcancel") - luaunit.assertEquals(event.phase, "cancelled") -end - --- Test: EventHandler tracks touch began -function TestTouchEvents:testEventHandler_TouchBegan() - FlexLove.beginFrame() - - local touchEvents = {} - local element = FlexLove.new({ - width = 200, - height = 200, - onEvent = function(el, event) - table.insert(touchEvents, event) - end, - }) - - FlexLove.endFrame() - - -- Simulate touch began - love.touch.getTouches = function() - return { "touch1" } - end - love.touch.getPosition = function(id) - if id == "touch1" then - return 100, 100 - end - return 0, 0 - end - - -- Trigger touch event processing - FlexLove.beginFrame() - element._eventHandler:processTouchEvents(element) - FlexLove.endFrame() - - -- Filter out hover/unhover events (from mouse processing) - local filteredEvents = {} - for _, event in ipairs(touchEvents) do - if event.type ~= "hover" and event.type ~= "unhover" then - table.insert(filteredEvents, event) - end - end - - -- Should have received at least one touchpress event - -- Note: May receive multiple events due to test state/frame processing - 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 - --- Test: EventHandler tracks touch moved -function TestTouchEvents:testEventHandler_TouchMoved() - FlexLove.beginFrame() - - local touchEvents = {} - local element = FlexLove.new({ - width = 200, - height = 200, - onEvent = function(el, event) - table.insert(touchEvents, event) - end, - }) - - FlexLove.endFrame() - - -- Simulate touch began - love.touch.getTouches = function() - return { "touch1" } - end - love.touch.getPosition = function(id) - if id == "touch1" then - return 100, 100 - end - return 0, 0 - end - - -- First touch - FlexLove.beginFrame() - element._eventHandler:processTouchEvents(element) - FlexLove.endFrame() - - -- Move touch - 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() - - -- Filter out hover/unhover events (from mouse processing) - local filteredEvents = {} - for _, event in ipairs(touchEvents) do - if event.type ~= "hover" and event.type ~= "unhover" then - table.insert(filteredEvents, event) - end - end - - -- Should have received touchpress and touchmove events - 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 - --- Test: EventHandler tracks touch ended -function TestTouchEvents:testEventHandler_TouchEnded() - FlexLove.beginFrame() - - local touchEvents = {} - local element = FlexLove.new({ - width = 200, - height = 200, - onEvent = function(el, event) - table.insert(touchEvents, event) - end, - }) - - FlexLove.endFrame() - - -- Simulate touch began - love.touch.getTouches = function() - return { "touch1" } - end - love.touch.getPosition = function(id) - if id == "touch1" then - return 100, 100 - end - return 0, 0 - end - - -- First touch - FlexLove.beginFrame() - element._eventHandler:processTouchEvents(element) - FlexLove.endFrame() - - -- End touch - love.touch.getTouches = function() - return {} - end - - FlexLove.beginFrame() - element._eventHandler:processTouchEvents(element) - FlexLove.endFrame() - - -- Filter out hover/unhover events (from mouse processing) - local filteredEvents = {} - for _, event in ipairs(touchEvents) do - if event.type ~= "hover" and event.type ~= "unhover" then - table.insert(filteredEvents, event) - end - end - - -- Should have received touchpress and touchrelease events - luaunit.assertEquals(#filteredEvents, 2) - luaunit.assertEquals(filteredEvents[1].type, "touchpress") - luaunit.assertEquals(filteredEvents[2].type, "touchrelease") -end - --- Test: EventHandler tracks multiple simultaneous touches -function TestTouchEvents:testEventHandler_MultiTouch() - 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() - - -- Simulate two touches - 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() - - -- Filter out hover/unhover events (from mouse processing) - local filteredEvents = {} - for _, event in ipairs(touchEvents) do - if event.type ~= "hover" and event.type ~= "unhover" then - table.insert(filteredEvents, event) - end - end - - -- Should have received two touchpress events (one for each touch) - luaunit.assertEquals(#filteredEvents, 2) - luaunit.assertEquals(filteredEvents[1].type, "touchpress") - luaunit.assertEquals(filteredEvents[2].type, "touchpress") - - -- Different touch IDs - luaunit.assertNotEquals(touchEvents[1].touchId, touchEvents[2].touchId) -end - --- Test: GestureRecognizer detects tap -function TestTouchEvents:testGestureRecognizer_Tap() - local GestureRecognizer = package.loaded["modules.GestureRecognizer"] - local InputEvent = package.loaded["modules.InputEvent"] - local utils = package.loaded["modules.utils"] - - local recognizer = GestureRecognizer.new({}, { - InputEvent = InputEvent, - utils = utils, - }) - - -- Simulate tap (press and quick release) - local touchId = "touch1" - local pressEvent = InputEvent.fromTouch(touchId, 100, 100, "began", 1.0) - local releaseEvent = InputEvent.fromTouch(touchId, 102, 102, "ended", 1.0) - - recognizer:processTouchEvent(pressEvent) - local gesture = recognizer:processTouchEvent(releaseEvent) - - -- Note: The gesture detection returns from internal methods, - -- needs to be captured from the event processing - -- This is a basic structural test - luaunit.assertNotNil(recognizer) -end - -if not _G.RUNNING_ALL_TESTS then - os.exit(luaunit.LuaUnit.run()) -end diff --git a/testing/__tests__/touch_routing_test.lua b/testing/__tests__/touch_routing_test.lua deleted file mode 100644 index 9e337c7..0000000 --- a/testing/__tests__/touch_routing_test.lua +++ /dev/null @@ -1,528 +0,0 @@ -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") - -FlexLove.init() - -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 - --- Test: touchpressed routes to element at position -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 - --- Test: touchmoved routes to owning element -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) - - -- Filter for move events - 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 - --- Test: touchreleased routes to owning element and cleans up -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") - - -- Filter for release events - 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 - --- Test: Touch ownership persists — move events route even outside element bounds -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() - - -- Press inside element - FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) - -- Move far outside element bounds - FlexLove.touchmoved("touch1", 500, 500, 400, 400, 1.0) - - -- Should still receive the move event due to ownership - 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 - --- Test: Touch outside all elements creates no ownership -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() - - -- Press outside element bounds - 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 - --- Test: Multiple touches route to different elements -function TestTouchRouting:test_multi_touch_different_elements() - FlexLove.beginFrame() - local events1 = {} - local events2 = {} - -- Two elements side by side (default row layout) - 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() - - -- Touch element1 (at x=0..200, y=0..200) - FlexLove.touchpressed("touch1", 50, 100, 0, 0, 1.0) - -- Touch element2 (at x=200..400, y=0..200) - 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 - --- Test: Z-index ordering — higher z element receives touch -function TestTouchRouting:test_z_index_ordering() - FlexLove.beginFrame() - local eventsLow = {} - local eventsHigh = {} - -- Lower z element - local low = FlexLove.new({ - width = 200, - height = 200, - z = 1, - onTouchEvent = function(el, event) - table.insert(eventsLow, event) - end, - }) - -- Higher z element overlapping - local high = FlexLove.new({ - width = 200, - height = 200, - z = 10, - onTouchEvent = function(el, event) - table.insert(eventsHigh, event) - end, - }) - FlexLove.endFrame() - - -- Touch overlapping area - 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 - --- Test: Disabled element does not receive touch -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 - --- Test: getActiveTouchCount tracks active touches -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 - --- Test: getTouchOwner returns correct element -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 - --- Test: destroy() cleans up touch state -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 - --- Test: Touch routing with onEvent (not just onTouchEvent) -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) - - -- onEvent should receive touch events via handleTouchEvent -> _invokeCallback - 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 - --- Test: Touch routing with onGesture callback -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() - - -- Simulate a quick tap (press and release at same position within threshold) - FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) - -- Small time step to avoid zero dt - love.timer.step(0.05) - FlexLove.touchreleased("touch1", 100, 100, 0, 0, 1.0) - - -- GestureRecognizer should detect a tap gesture - 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 - --- Test: touchpressed with no onTouchEvent but onGesture — should still find element -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 - --- Test: touchEnabled=false prevents touch routing -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 - --- Test: Complete touch lifecycle (press, move, release) -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 - --- Test: Orphaned move/release with no owner (no crash) -function TestTouchRouting:test_orphaned_move_release_no_crash() - -- Move and release events with no prior press should not 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 - --- Test: Pressure value is passed through -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 - --- Test: Retained mode touch routing -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 - --- Test: Child element receives touch over parent -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, -- Ensure child has higher z than parent - onTouchEvent = function(el, event) - table.insert(childEvents, event) - end, - parent = parent, - }) - FlexLove.endFrame() - - -- Touch within child area (which is also within parent) - FlexLove.touchpressed("touch1", 100, 100, 0, 0, 1.0) - - -- Child has explicit higher z, should receive touch - luaunit.assertTrue(#childEvents >= 1, - string.format("Child should receive touch (child=%d, parent=%d, topElements=%d)", - #childEvents, #parentEvents, #FlexLove.topElements)) -end - --- Test: Element with no callbacks not found by touch routing -function TestTouchRouting:test_non_interactive_element_ignored() - FlexLove.beginFrame() - -- Element with no onEvent, onTouchEvent, or onGesture - 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 - -if not _G.RUNNING_ALL_TESTS then - os.exit(luaunit.LuaUnit.run()) -end diff --git a/testing/__tests__/touch_scroll_test.lua b/testing/__tests__/touch_scroll_test.lua deleted file mode 100644 index 10635f1..0000000 --- a/testing/__tests__/touch_scroll_test.lua +++ /dev/null @@ -1,475 +0,0 @@ -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 ScrollManager = require("modules.ScrollManager") -local Color = require("modules.Color") -local utils = require("modules.utils") - --- Initialize ScrollManager with ErrorHandler -ScrollManager.init({ ErrorHandler = ErrorHandler }) - --- Helper to create ScrollManager with touch config -local function createTouchScrollManager(config) - config = config or {} - config.overflow = config.overflow or "scroll" - return ScrollManager.new(config, { - Color = Color, - utils = utils, - }) -end - --- Helper to create mock element with content taller than container -local function createMockElement(width, height, contentWidth, contentHeight) - local children = {} - -- Create a single child that represents all content - 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 - --- ============================================================================ --- Test Suite: Touch Press --- ============================================================================ - -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) - - -- Simulate momentum by starting touch, moving fast, releasing - sm:handleTouchPress(100, 200) - -- Manually set momentum state - sm._momentumScrolling = true - sm._scrollVelocityY = 500 - - -- New press should stop momentum - sm:handleTouchPress(100, 200) - - luaunit.assertFalse(sm:isMomentumScrolling()) - luaunit.assertEquals(sm._scrollVelocityX, 0) - luaunit.assertEquals(sm._scrollVelocityY, 0) -end - --- ============================================================================ --- Test Suite: Touch Move --- ============================================================================ - -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) - - -- Advance time so dt > 0 in handleTouchMove - love.timer.step(1 / 60) - local handled = sm:handleTouchMove(100, 150) - - luaunit.assertTrue(handled) - -- Touch moved UP by 50px, so scroll should increase (content moves down relative to finger) - 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) -- Move 100px up - - -- Velocity should be set (non-zero since time elapsed) - -- Note: velocity direction is inverted (touch up = scroll down = positive velocity) - 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) -- Wide content - sm:detectOverflow(el) - - sm:handleTouchPress(200, 150) - love.timer.step(1 / 60) - sm:handleTouchMove(100, 150) -- Move 100px left - - 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) - - -- Scroll is at 0 (top), try to scroll further up (negative) - sm:handleTouchPress(100, 100) - love.timer.step(1 / 60) - sm:handleTouchMove(100, 200) -- Move down = scroll up (negative) - - -- With bounce, overscroll should be allowed (scroll < 0) - luaunit.assertTrue(sm._scrollY < 0) -end - --- ============================================================================ --- Test Suite: Touch Release and Momentum --- ============================================================================ - -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) -- Fast swipe up - - sm:handleTouchRelease() - - -- Should start momentum scrolling due to high velocity - 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) - -- Simulate a very slow move by setting low velocity manually - sm._scrollVelocityX = 0 - sm._scrollVelocityY = 10 -- Below threshold of 50 - - 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 - --- ============================================================================ --- Test Suite: Momentum Scrolling --- ============================================================================ - -TestMomentumScrolling = {} - -function TestMomentumScrolling:setUp() - love.timer.setTime(0) -end - -function TestMomentumScrolling:test_momentum_decelerates_over_time() - local sm = createTouchScrollManager({ bounceEnabled = false }) - local el = createMockElement() - sm:detectOverflow(el) - - -- Set up momentum manually - 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 TestMomentumScrolling:test_momentum_stops_at_low_velocity() - local sm = createTouchScrollManager({ bounceEnabled = false }) - local el = createMockElement() - sm:detectOverflow(el) - - sm._momentumScrolling = true - sm._scrollVelocityY = 200 - - -- Run many frames until momentum stops - 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 TestMomentumScrolling: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 TestMomentumScrolling: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) - - -- Higher friction (0.99) preserves more velocity than lower friction (0.90) - luaunit.assertTrue(smFast._scrollVelocityY > smSlow._scrollVelocityY) -end - --- ============================================================================ --- Test Suite: Bounce Effects --- ============================================================================ - -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) - - -- Force overscroll position - sm._scrollY = -50 - - -- Run bounce updates - for i = 1, 100 do - sm:update(1 / 60) - end - - -- Should have bounced back to 0 - 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) - - -- Force overscroll past max - 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) - - -- Without bounce, scroll should stay where it is (clamped by scrollBy) - -- But here we set it directly, so it stays - luaunit.assertEquals(sm._scrollY, -50) -end - --- ============================================================================ --- Test Suite: State Query Methods --- ============================================================================ - -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 - --- ============================================================================ --- Test Suite: Configuration --- ============================================================================ - -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 - --- Run all tests -if not _G.RUNNING_ALL_TESTS then - os.exit(luaunit.LuaUnit.run()) -end diff --git a/testing/__tests__/touch_test.lua b/testing/__tests__/touch_test.lua new file mode 100644 index 0000000..64998e4 --- /dev/null +++ b/testing/__tests__/touch_test.lua @@ -0,0 +1,1902 @@ +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 diff --git a/testing/runAll.lua b/testing/runAll.lua index 930930f..cdaf68d 100644 --- a/testing/runAll.lua +++ b/testing/runAll.lua @@ -44,12 +44,10 @@ local testFiles = { "testing/__tests__/calc_test.lua", "testing/__tests__/critical_failures_test.lua", "testing/__tests__/element_test.lua", - "testing/__tests__/element_touch_test.lua", "testing/__tests__/element_mode_override_test.lua", "testing/__tests__/event_handler_test.lua", "testing/__tests__/flex_grow_shrink_test.lua", "testing/__tests__/flexlove_test.lua", - "testing/__tests__/gesture_recognizer_test.lua", "testing/__tests__/grid_test.lua", "testing/__tests__/image_cache_test.lua", "testing/__tests__/image_renderer_test.lua", @@ -70,9 +68,7 @@ local testFiles = { "testing/__tests__/scrollbar_placement_test.lua", "testing/__tests__/text_editor_test.lua", "testing/__tests__/theme_test.lua", - "testing/__tests__/touch_events_test.lua", - "testing/__tests__/touch_routing_test.lua", - "testing/__tests__/touch_scroll_test.lua", + "testing/__tests__/touch_test.lua", "testing/__tests__/transition_test.lua", "testing/__tests__/units_test.lua", "testing/__tests__/utils_test.lua",