1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Interaction tests — driving the controls, not looking at them.
//!
//! Every static axis of this port is measured by the `.shots/*.py` audits;
//! what they cannot see is whether a control *functions*. These tests open a
//! real gpui window on the headless test platform and simulate clicks and
//! keystrokes against it. The window, the recorder type and the input helpers
//! live in the shared `tests/harness/mod.rs` module.
use crate::harness;
use std::cell::RefCell;
use std::rc::Rc;
use gpui::{point, prelude::*, px, KeyUpEvent, Keystroke, Modifiers, TestAppContext};
use herogpui_components::{Checkbox, PickerItem, Select, Switch};
use harness::{open_host, Events};
#[gpui::test]
fn checkbox_click_toggles(cx: &mut TestAppContext) {
let events: Events = Rc::new(RefCell::new(Vec::new()));
let recorded = events.clone();
let cx = open_host(cx, move || {
let events = events.clone();
Checkbox::new("cb")
.label("Toggle")
.on_change(move |checked, _, _| {
events.borrow_mut().push(format!("change:{checked}"));
})
.into_any_element()
});
// The row starts at the window origin and the 16px control box is its
// first child, centred at (8, 8).
cx.simulate_click(point(px(8.), px(8.)), Modifiers::none());
assert_eq!(
recorded.borrow().as_slice(),
["change:true"],
"first click must check the box"
);
cx.simulate_click(point(px(8.), px(8.)), Modifiers::none());
assert_eq!(
recorded.borrow().as_slice(),
["change:true", "change:false"],
"second click must uncheck it"
);
}
#[gpui::test]
fn switch_keyboard_activates(cx: &mut TestAppContext) {
let events: Events = Rc::new(RefCell::new(Vec::new()));
let recorded = events.clone();
let cx = open_host(cx, move || {
let events = events.clone();
Switch::new("sw")
.label("Wi-Fi")
.on_change(move |checked, _, _| {
events.borrow_mut().push(format!("change:{checked}"));
})
.into_any_element()
});
// Tab moves the focus from the harness root onto the track without firing
// anything (a click would have toggled it once already). Space then
// activates the focused track exactly once: gpui fires a focused element's
// click listeners on Enter and Space, and a component that also bound its
// own key handler would fire twice — the double-fire bug class this guards.
//
// The activation listens on key *up*, and `dispatch_keystroke` sends only
// the down half, so the release is delivered explicitly.
cx.simulate_keystrokes("tab space");
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse("space").unwrap(),
});
assert_eq!(
recorded.borrow().as_slice(),
["change:true"],
"one keystroke must toggle the switch exactly once"
);
}
#[gpui::test]
fn select_click_selects_and_closes(cx: &mut TestAppContext) {
let events: Events = Rc::new(RefCell::new(Vec::new()));
let recorded = events.clone();
let cx = open_host(cx, move || {
let selection = events.clone();
let opening = events.clone();
Select::new(
"sel",
vec![PickerItem::new("rust", "Rust"), PickerItem::new("go", "Go")],
)
.on_selection_change(move |key, _, _| {
let key = key
.as_ref()
.map_or_else(|| "none".to_owned(), ToString::to_string);
selection.borrow_mut().push(format!("select:{key}"));
})
.on_open_change(move |open, _, _| opening.borrow_mut().push(format!("open:{open}")))
.into_any_element()
});
// The trigger is a full-width row of `util::FIELD_HEIGHT` (36px) starting
// at the origin; its centre is (60, 18).
cx.simulate_click(point(px(60.), px(18.)), Modifiers::none());
assert_eq!(recorded.borrow().as_slice(), ["open:true"]);
// First option: `placed_field_panel` hangs the panel 6px below the trigger
// (`top_full` + `mt(6px)` => y 42) with 6px vertical padding and
// `min_h(FIELD_HEIGHT)` rows, so the row spans roughly y 48..84 and its
// centre sits near y 66. The enter zoom animates only padding and radius,
// never enough to move that band.
cx.simulate_click(point(px(60.), px(66.)), Modifiers::none());
assert_eq!(
recorded.borrow().as_slice(),
["open:true", "open:false", "select:rust"],
"clicking the first row must report the close and select its key"
);
// Closed proof by behaviour: the same spot is bare page below the trigger
// now, so the press must reach nothing. Were the popover still open, the
// row would record a second `select:rust` here. The callback proves it
// too since the row reports its own dismissal, but the probe is what shows
// the panel has actually left the tree.
cx.simulate_click(point(px(60.), px(66.)), Modifiers::none());
assert_eq!(
recorded.borrow().as_slice(),
["open:true", "open:false", "select:rust"],
"the popover must be closed after choosing an option"
);
}