use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size}; use iris::harness::{Harness, TouchAction, TouchScript}; use iris::prelude::*; use iris::sense::DRAG_SLOP; fn opened() -> (Harness, ai_app::ui::TranscriptScreen) { let mut h = Harness::new(phone_size(), PHONE_SCALE); let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds"); h.frame(0); h.frame(PHONE_FRAME_MS); (h, opened.screen) } fn tracked_row(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen) -> (RowKey, f32) { let middle = phone_size().y / 2.0; let list = (screen.list)(&mut h.rsc); let key = list.key_at(middle).expect("a row under the viewport"); let (top, _) = list.extent(key).expect("that row has an extent"); (key, top) } fn row_top(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen, key: RowKey) -> f32 { (screen.list)(&mut h.rsc) .extent(key) .expect("the tracked row is still loaded") .0 } const STEP: f32 = 2.0; const SAMPLES: usize = 3; const CATCH_X: f32 = 540.0; fn drag_from( h: &mut Harness, screen: &ai_app::ui::TranscriptScreen, key: RowKey, y0: f32, t0: u64, expect_tracking: bool, ) -> u64 { let before = row_top(h, screen, key); h.touch(TouchAction::Down, Vec2::new(CATCH_X, y0), t0); assert_eq!( row_top(h, screen, key), before, "the down itself must not move the content, only stop it" ); let mut t = t0; for i in 1..=SAMPLES { let moved = STEP * i as f32; t = t0 + 8 * i as u64; h.touch(TouchAction::Move, Vec2::new(CATCH_X, y0 + moved), t); let travelled = row_top(h, screen, key) - before; if expect_tracking { assert!( (travelled - moved).abs() < 0.5, "sample {i}: the finger has moved {moved}px since the down and the content \ {travelled:.1}px -- it is not pinned to the finger" ); } else { assert!( travelled.abs() < 0.5, "sample {i}: a {moved}px drag is inside DRAG_SLOP ({DRAG_SLOP}px) and must move \ nothing, but the content moved {travelled:.1}px" ); } } t += 8; h.touch( TouchAction::Up, Vec2::new(CATCH_X, y0 + STEP * SAMPLES as f32), t, ); t } #[test] fn a_press_on_a_flinging_list_pins_the_content_to_the_finger() { let (mut h, screen) = opened(); let flick = TouchScript::parse(include_str!("../touch/flick-120hz.touch")) .unwrap_or_else(|e| panic!("flick-120hz.touch: {e}")); h.replay(&flick); let catch_at = flick.end_ms() + 150; h.frames_until( flick.end_ms() + PHONE_FRAME_MS, catch_at - PHONE_FRAME_MS, PHONE_FRAME_MS, ); assert!( (screen.list)(&mut h.rsc).is_scrolling(), "the fling must still be running 150ms in, or this test catches nothing" ); let (key, _) = tracked_row(&mut h, &screen); drag_from(&mut h, &screen, key, 1200.0, catch_at, true); } #[test] fn a_catch_that_never_moved_is_not_a_tap_and_does_not_fling() { let (mut h, screen) = opened(); let flick = TouchScript::parse(include_str!("../touch/flick-120hz.touch")) .unwrap_or_else(|e| panic!("flick-120hz.touch: {e}")); h.replay(&flick); let catch_at = flick.end_ms() + 150; h.frames_until( flick.end_ms() + PHONE_FRAME_MS, catch_at - PHONE_FRAME_MS, PHONE_FRAME_MS, ); assert!( (screen.list)(&mut h.rsc).is_scrolling(), "the fling must still be running 150ms in, or this test catches nothing" ); let (key, _) = tracked_row(&mut h, &screen); h.touch(TouchAction::Down, Vec2::new(CATCH_X, 1200.0), catch_at); let stopped_at = row_top(&mut h, &screen, key); h.touch(TouchAction::Up, Vec2::new(CATCH_X, 1200.0), catch_at + 8); assert_eq!( (screen.list)(&mut h.rsc).fling_velocity(), None, "a press that stopped a fling and moved nothing must not start another" ); h.frames_until(catch_at + 16, catch_at + 500, PHONE_FRAME_MS); assert!( (row_top(&mut h, &screen, key) - stopped_at).abs() < 0.5, "the content moved after a catch was released without moving" ); assert_eq!( h.state.opened_urls, Vec::::new(), "a catch is not a tap: nothing under it may be followed" ); } #[test] fn the_same_small_drag_on_a_settled_list_moves_nothing() { let (mut h, screen) = opened(); let flick = TouchScript::parse(include_str!("../touch/flick-120hz.touch")) .unwrap_or_else(|e| panic!("flick-120hz.touch: {e}")); h.replay(&flick); let settled = h.frames_until( flick.end_ms() + PHONE_FRAME_MS, flick.end_ms() + 4000, PHONE_FRAME_MS, ); assert!( !(screen.list)(&mut h.rsc).is_scrolling(), "the fling must have stopped, or this is the same case as the test above" ); let (key, _) = tracked_row(&mut h, &screen); drag_from( &mut h, &screen, key, 1200.0, settled + PHONE_FRAME_MS, false, ); }