Make the Rust client the sole app
This commit is contained in:
1 parent
a8602c1626
commit
d8bb1699a8
230 files changed
+762
-27300
No files matched your search
@@ -0,0 +1,193 @@
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchAction};
|
||||
use iris::prelude::*;
|
||||
|
||||
fn fence_scroll_in(h: &Harness, top: f32, bottom: f32) -> Option<(WidgetId, PixelRegion)> {
|
||||
let render_handle = h.rsc.ui.render_state();
|
||||
let render_state = render_handle.get();
|
||||
render_state
|
||||
.active
|
||||
.keys()
|
||||
.copied()
|
||||
.filter(|&id| {
|
||||
h.rsc
|
||||
.ui
|
||||
.widgets
|
||||
.get_dyn(id)
|
||||
.and_then(|w| w.as_any().downcast_ref::<ScrollArea>())
|
||||
.is_some_and(|s| s.axis() == Axis::X)
|
||||
})
|
||||
.find_map(|id| {
|
||||
let r = render_state.window_region(&id, &h.rsc)?;
|
||||
(r.top_left.y >= top && r.bot_right.y <= bottom).then_some((id, r))
|
||||
})
|
||||
}
|
||||
|
||||
fn is_scrolling(h: &Harness, id: WidgetId) -> bool {
|
||||
h.rsc
|
||||
.ui
|
||||
.widgets
|
||||
.get_dyn(id)
|
||||
.and_then(|w| w.as_any().downcast_ref::<ScrollArea>())
|
||||
.expect("the fence's scroll area is still drawn")
|
||||
.is_scrolling()
|
||||
}
|
||||
|
||||
fn amt(h: &Harness, id: WidgetId) -> f32 {
|
||||
h.rsc
|
||||
.ui
|
||||
.widgets
|
||||
.get_dyn(id)
|
||||
.and_then(|w| w.as_any().downcast_ref::<ScrollArea>())
|
||||
.expect("the fence's scroll area is still drawn")
|
||||
.amt()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_flick_across_a_code_fence_keeps_moving_after_the_finger_leaves() {
|
||||
use ai_app::client::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
|
||||
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");
|
||||
let mut screen = opened.screen;
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
|
||||
let fence = TranscriptRow::Single(TranscriptItem::AssistantMsg {
|
||||
seq: 9_000_000,
|
||||
text: "```\none two three four five six seven eight nine ten eleven twelve \
|
||||
thirteen fourteen fifteen sixteen seventeen eighteen twenty twentyone\n```"
|
||||
.to_string(),
|
||||
settled: true,
|
||||
});
|
||||
screen.push_row(&mut h.rsc, &fence);
|
||||
(screen.list)(&mut h.rsc).jump_to_end();
|
||||
h.frame(100);
|
||||
h.frame(108);
|
||||
|
||||
let key = ai_app::ui::row::row_key(&fence.key());
|
||||
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the fence row is on screen");
|
||||
let (fence_scroll, box_) = fence_scroll_in(&h, top, bottom)
|
||||
.expect("the pushed fence draws a horizontal scroll area of its own");
|
||||
assert_eq!(amt(&h, fence_scroll), 0.0, "a fence opens at its start");
|
||||
|
||||
let y = (box_.top_left.y + box_.bot_right.y) / 2.0;
|
||||
|
||||
h.touch(TouchAction::Down, Vec2::new(900.0, y), 200);
|
||||
for (i, x) in [860.0, 800.0, 720.0, 620.0].into_iter().enumerate() {
|
||||
h.touch(TouchAction::Move, Vec2::new(x, y), 208 + 8 * i as u64);
|
||||
}
|
||||
h.touch(TouchAction::Up, Vec2::new(620.0, y), 240);
|
||||
|
||||
let at_release = amt(&h, fence_scroll);
|
||||
assert!(
|
||||
at_release > 0.0,
|
||||
"the flick itself must have panned the fence, got {at_release}"
|
||||
);
|
||||
|
||||
let mut t = 240;
|
||||
while t <= 740 {
|
||||
h.frame(t);
|
||||
t += PHONE_FRAME_MS;
|
||||
}
|
||||
let coasted = amt(&h, fence_scroll);
|
||||
assert!(
|
||||
coasted > at_release + 1.0,
|
||||
"the fence stopped dead at the release: {at_release} -> {coasted}"
|
||||
);
|
||||
|
||||
let settled = coasted;
|
||||
while t <= 4_000 {
|
||||
h.frame(t);
|
||||
t += PHONE_FRAME_MS;
|
||||
}
|
||||
let after = amt(&h, fence_scroll);
|
||||
assert!(
|
||||
after >= settled,
|
||||
"a fling must not run backwards: {settled} -> {after}"
|
||||
);
|
||||
let last = after;
|
||||
h.frame(t);
|
||||
assert_eq!(last, amt(&h, fence_scroll), "the fling never settled");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_drag_away_from_a_coasting_fence_scrolls_the_list_and_leaves_it_coasting() {
|
||||
use ai_app::client::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
|
||||
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");
|
||||
let mut screen = opened.screen;
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
|
||||
let fence = TranscriptRow::Single(TranscriptItem::AssistantMsg {
|
||||
seq: 9_000_000,
|
||||
text: format!(
|
||||
"```\n{}\n```",
|
||||
(1..=200)
|
||||
.map(|i| format!("word{i}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
settled: true,
|
||||
});
|
||||
screen.push_row(&mut h.rsc, &fence);
|
||||
(screen.list)(&mut h.rsc).jump_to_end();
|
||||
h.frame(100);
|
||||
h.frame(108);
|
||||
|
||||
let key = ai_app::ui::row::row_key(&fence.key());
|
||||
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the fence row is on screen");
|
||||
let (fence_scroll, box_) = fence_scroll_in(&h, top, bottom)
|
||||
.expect("the pushed fence draws a horizontal scroll area of its own");
|
||||
let y = (box_.top_left.y + box_.bot_right.y) / 2.0;
|
||||
|
||||
h.touch(TouchAction::Down, Vec2::new(900.0, y), 200);
|
||||
for (i, x) in [860.0, 800.0, 720.0, 620.0].into_iter().enumerate() {
|
||||
h.touch(TouchAction::Move, Vec2::new(x, y), 208 + 8 * i as u64);
|
||||
}
|
||||
h.touch(TouchAction::Up, Vec2::new(620.0, y), 240);
|
||||
h.frame(248);
|
||||
assert!(
|
||||
is_scrolling(&h, fence_scroll),
|
||||
"the fence has to still be coasting for this to be the reported case",
|
||||
);
|
||||
|
||||
let probe = box_.top_left.y - 500.0;
|
||||
let row = (screen.list)(&mut h.rsc)
|
||||
.key_at(probe)
|
||||
.expect("a row that far up the screen");
|
||||
let (row_top, row_bottom) = (screen.list)(&mut h.rsc).extent(row).expect("its extent");
|
||||
let from = (row_top + row_bottom) / 2.0;
|
||||
|
||||
let list_before = (screen.list)(&mut h.rsc).anchor_position_display();
|
||||
let fence_before = amt(&h, fence_scroll);
|
||||
h.touch(TouchAction::Down, Vec2::new(540.0, from), 256);
|
||||
let mut t = 264;
|
||||
for i in 1..=8 {
|
||||
h.touch(
|
||||
TouchAction::Move,
|
||||
Vec2::new(540.0, from + 20.0 * i as f32),
|
||||
t,
|
||||
);
|
||||
t += 8;
|
||||
}
|
||||
h.touch(TouchAction::Up, Vec2::new(540.0, from + 160.0), t);
|
||||
|
||||
assert_ne!(
|
||||
list_before,
|
||||
(screen.list)(&mut h.rsc).anchor_position_display(),
|
||||
"the drag was nowhere near the fence, so it belongs to the list",
|
||||
);
|
||||
assert!(
|
||||
amt(&h, fence_scroll) > fence_before,
|
||||
"the fence's fling must carry on through a gesture that was never \
|
||||
its own: {fence_before} -> {}",
|
||||
amt(&h, fence_scroll),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user